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//! ## Locking
51//!
52//! [`Pallet::add_lock`] shuts the manager out of a registered para, leaving it to the para's own
53//! governance. Only root or the para itself can lift it again with [`Pallet::remove_lock`].
54//!
55//! A para is also locked the first time the relay chain reports that it produced a head, which
56//! arrives as [`MessageToParaV1::HeadNoted`]. A lock lifted with [`Pallet::remove_lock`] outranks
57//! that, so it is never re-applied.
58//!
59//! Deposits only ever live on this chain; the relay chain takes nothing.
60
61#![cfg_attr(not(feature = "std"), no_std)]
62
63extern crate alloc;
64
65use alloc::vec::Vec;
66use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
67use frame_support::{
68	ensure,
69	traits::{Consideration, EnsureOrigin, Footprint},
70};
71use registrar_primitives::{
72	FailureReason, MessageToPara, MessageToParaV1, MessageToRelay, MessageToRelayV1, Outcome,
73	ParaId,
74};
75use scale_info::TypeInfo;
76use sp_core::H256;
77use sp_runtime::{
78	traits::{BlockNumberProvider, Saturating},
79	DispatchResult,
80};
81
82pub use pallet::*;
83pub use weights::WeightInfo;
84
85pub mod weights;
86
87#[cfg(feature = "runtime-benchmarks")]
88mod benchmarking;
89#[cfg(test)]
90mod mock;
91#[cfg(test)]
92mod tests;
93
94/// Block number used for registration deadlines.
95///
96/// On a parachain, configure [`Config::BlockNumberProvider`] to
97/// `cumulus_pallet_parachain_system::RelaychainDataProvider`, so deadlines are expressed in
98/// relay-chain blocks and keep their meaning through a stall in this chain's own block production.
99pub type ProvidedBlockNumberOf<T> =
100	<<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
101
102/// Used to send an XCM `Transact` to the registrar pallet on the remote relay chain.
103pub trait SendToRelay {
104	/// The account id used to identify a registration's manager on both chains.
105	type AccountId;
106
107	/// Send `message` to the relay chain.
108	///
109	/// `Err(())` means the message could not be handed to the transport at all. Callers are
110	/// expected to fail the whole extrinsic, so nothing is left half-done.
111	#[allow(clippy::result_unit_err)]
112	fn send(message: MessageToRelay<Self::AccountId>) -> Result<(), ()>;
113}
114
115#[cfg(feature = "std")]
116impl SendToRelay for () {
117	type AccountId = sp_runtime::AccountId32;
118
119	fn send(_message: MessageToRelay<Self::AccountId>) -> Result<(), ()> {
120		Ok(())
121	}
122}
123
124/// Where a para id sits in the registration flow.
125#[derive(
126	Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen,
127)]
128pub enum RegistrationState<Ticket, BlockNumber> {
129	/// The para id is held by its manager, but nothing is registered on the relay chain yet.
130	Reserved,
131	/// The relay chain has been asked to register this para and has not reported back.
132	Pending {
133		/// The registration's [`Consideration`] ticket, returned if the registration fails.
134		ticket: Ticket,
135		/// The block from which the manager may give up on this registration.
136		///
137		/// Expressed in [`Config::BlockNumberProvider`] blocks. Long enough that a verdict already
138		/// on its way arrives first, so a cancellation is only ever sent for a registration that
139		/// really has gone quiet. Pushed out again by every [`Pallet::cancel_registration`], so a
140		/// cancellation that gets lost can be retried but not spammed.
141		cancellable_at: BlockNumber,
142		/// The request this state is waiting on; a response carrying any other id is stale.
143		message_id: u64,
144	},
145	/// The relay chain has onboarded this para.
146	Registered {
147		/// The registration's [`Consideration`] ticket, kept while the para is registered.
148		ticket: Ticket,
149	},
150	/// The relay chain has been asked to drop this para and has not reported back.
151	///
152	/// Both deposits stay held: only the relay chain knows whether the para really went away, and
153	/// a refusal puts it straight back to [`RegistrationState::Registered`].
154	///
155	/// There is nothing to cancel from here: if no answer turns up, the manager calls
156	/// [`Pallet::deregister`] again once `can_retry_after` has passed.
157	Deregistering {
158		/// The registration's [`Consideration`] ticket, released once the relay chain confirms.
159		ticket: Ticket,
160		/// The block from which the manager may send the [`MessageToRelayV1::Deregister`] again,
161		/// if the answer never arrived. Retrying reports the missing answer as
162		/// [`UnexpectedKind::ResponseNeverArrived`].
163		can_retry_after: BlockNumber,
164		/// The request this state is waiting on; a response carrying any other id is stale.
165		message_id: u64,
166	},
167}
168
169/// Everything this chain knows about one para id.
170#[derive(
171	Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo, MaxEncodedLen, Debug,
172)]
173pub struct ParaInfo<AccountId, ReservationTicket, RegistrationTicket, BlockNumber> {
174	/// The account that reserved the para id and controls it.
175	pub manager: AccountId,
176	/// The [`Consideration`] ticket for the para id itself.
177	pub reservation: ReservationTicket,
178	/// Where this para id sits in the registration flow.
179	pub state: RegistrationState<RegistrationTicket, BlockNumber>,
180	/// Whether the manager is locked out of controlling this para. `None` until the lock is set
181	/// for the first time, and read as unlocked.
182	pub locked: Option<bool>,
183}
184
185impl<AccountId, ReservationTicket, RegistrationTicket, BlockNumber>
186	ParaInfo<AccountId, ReservationTicket, RegistrationTicket, BlockNumber>
187{
188	/// Whether the manager is locked out of this para.
189	pub fn is_locked(&self) -> bool {
190		self.locked.unwrap_or(false)
191	}
192}
193
194/// The [`ParaInfo`] type as configured.
195pub type ParaInfoOf<T> = ParaInfo<
196	<T as frame_system::Config>::AccountId,
197	<T as Config>::ReservationConsideration,
198	<T as Config>::RegistrationConsideration,
199	ProvidedBlockNumberOf<T>,
200>;
201
202#[frame_support::pallet]
203pub mod pallet {
204	use super::*;
205	use frame_support::pallet_prelude::{DispatchResult, *};
206	use frame_system::pallet_prelude::*;
207
208	#[pallet::config]
209	pub trait Config: frame_system::Config {
210		/// The cost of reserving a para id. The footprint is a single zero-sized item, so a flat
211		/// price fits.
212		type ReservationConsideration: Consideration<Self::AccountId, Footprint>;
213
214		/// The cost of a registration, on top of the reservation. The footprint is one item sized
215		/// as head data plus *declared* code length, so a per-byte price fits.
216		type RegistrationConsideration: Consideration<Self::AccountId, Footprint>;
217
218		/// Sends messages to the relay chain.
219		type SendToRelay: SendToRelay<AccountId = Self::AccountId>;
220
221		/// An origin that is sure to be the relay chain's registrar pallet.
222		type RelayOrigin: EnsureOrigin<Self::RuntimeOrigin>;
223
224		/// An origin a parachain uses to act as itself, resolved to its para id.
225		type ParachainOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ParaId>;
226
227		/// The lowest para id this pallet will hand out.
228		///
229		/// Mirrors the relay chain's `LOWEST_PUBLIC_ID`. Ids below it are reserved for system
230		/// parachains and are not obtainable here.
231		#[pallet::constant]
232		type FirstPublicParaId: Get<ParaId>;
233
234		/// The smallest validation code the relay chain will accept.
235		///
236		/// A local mirror of the relay chain's `MIN_CODE_SIZE`, used to fail early. The relay
237		/// chain checks the real thing against its own live configuration.
238		#[pallet::constant]
239		type MinCodeSize: Get<u32>;
240
241		/// The largest validation code the relay chain will accept.
242		///
243		/// A local mirror of the relay chain's `max_code_size`. See [`Config::MinCodeSize`].
244		#[pallet::constant]
245		type MaxCodeSize: Get<u32>;
246
247		/// The largest head data the relay chain will accept.
248		///
249		/// A local mirror of the relay chain's `max_head_data_size`. See [`Config::MinCodeSize`].
250		#[pallet::constant]
251		type MaxHeadDataSize: Get<u32>;
252
253		/// How long a manager waits for the relay chain before giving up on a registration.
254		///
255		/// Measured in [`Config::BlockNumberProvider`] blocks. Should comfortably cover a round
256		/// trip, so that a verdict that is merely slow lands before anybody tries to cancel.
257		#[pallet::constant]
258		type PendingDeadline: Get<ProvidedBlockNumberOf<Self>>;
259
260		/// Source of block numbers for registration deadlines.
261		///
262		/// On a parachain this should be
263		/// `cumulus_pallet_parachain_system::RelaychainDataProvider`, so
264		/// [`Config::PendingDeadline`] is in relay-chain blocks.
265		type BlockNumberProvider: BlockNumberProvider;
266
267		/// Weight information for the extrinsics in this pallet.
268		type WeightInfo: WeightInfo;
269	}
270
271	#[pallet::pallet]
272	pub struct Pallet<T>(_);
273
274	/// Hold reasons for runtimes that pay the considerations out of held funds.
275	#[pallet::composite_enum]
276	pub enum HoldReason {
277		/// Held for keeping a para id reserved.
278		#[codec(index = 0)]
279		ParaIdReservation,
280		/// Held for the head data and validation code of a registration.
281		#[codec(index = 1)]
282		Registration,
283	}
284
285	/// The next para id that [`Pallet::reserve`] will hand out.
286	#[pallet::storage]
287	pub type NextFreeParaId<T: Config> = StorageValue<_, ParaId, ValueQuery>;
288
289	/// The id the next message to the relay chain will carry.
290	///
291	/// One per message sent, echoed back in the relay chain's response, so a request, its
292	/// response and the events on both chains can be tied together.
293	#[pallet::storage]
294	pub type NextMessageId<T: Config> = StorageValue<_, u64, ValueQuery>;
295
296	/// Every para id reserved through this pallet, and what is happening with it.
297	#[pallet::storage]
298	pub type Paras<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, ParaInfoOf<T>>;
299
300	#[pallet::event]
301	#[pallet::generate_deposit(pub(super) fn deposit_event)]
302	pub enum Event<T: Config> {
303		/// A para id was reserved.
304		Reserved { para_id: ParaId, who: T::AccountId },
305		/// A registration was requested and the relay chain has been asked to accept it.
306		RegisterRequested { para_id: ParaId, message_id: u64, manager: T::AccountId },
307		/// The relay chain confirmed a registration.
308		Registered { para_id: ParaId, message_id: u64, manager: T::AccountId },
309		/// The relay chain rejected a registration. The registration consideration was returned.
310		RegistrationFailed {
311			para_id: ParaId,
312			message_id: u64,
313			manager: T::AccountId,
314			reason: FailureReason,
315		},
316		/// A manager gave up on a pending registration, and the relay chain has been asked to
317		/// drop the authorization. The consideration stays taken until it answers.
318		CancelRequested { para_id: ParaId, message_id: u64, manager: T::AccountId },
319		/// The relay chain confirmed a cancellation. The registration consideration was returned.
320		RegistrationCancelled { para_id: ParaId, message_id: u64, manager: T::AccountId },
321		/// The manager is locked out of this para.
322		ParaLocked { para_id: ParaId },
323		/// The manager may control this para again.
324		ParaUnlocked { para_id: ParaId },
325		/// Something that should never happen did. The pallet carried on regardless.
326		Unexpected(UnexpectedKind),
327	}
328
329	/// A defensive check that failed, reported as [`Event::Unexpected`] so it is visible on chain
330	/// and not just in the node's logs.
331	#[derive(
332		Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen,
333	)]
334	pub enum UnexpectedKind {
335		/// A register response for a para id this pallet does not know.
336		RegisterResponseForUnknownPara { para_id: ParaId, message_id: u64 },
337		/// A register response for a para with no registration in flight.
338		RegisterResponseNotPending { para_id: ParaId, message_id: u64 },
339		/// A head was noted for a para id this pallet does not know.
340		HeadNotedForUnknownPara { para_id: ParaId },
341		/// A cancel response for a para id this pallet does not know.
342		CancelResponseForUnknownPara { para_id: ParaId, message_id: u64 },
343		/// The relay chain refused a cancellation for a reason that is not one of the refusals
344		/// this pallet knows how to settle.
345		CancelRefused { para_id: ParaId, message_id: u64, reason: FailureReason },
346		/// A head was noted for a para that is not registered.
347		HeadNotedForUnregisteredPara { para_id: ParaId },
348		/// A response answering a request this pallet is no longer waiting on.
349		StaleResponse { para_id: ParaId, message_id: u64, expected: u64 },
350		/// The relay chain never answered this request and the manager gave up waiting.
351		ResponseNeverArrived { para_id: ParaId, message_id: u64 },
352	}
353
354	#[pallet::error]
355	pub enum Error<T> {
356		/// The para id has not been reserved.
357		NotReserved,
358		/// The caller does not manage this para id.
359		NotOwner,
360		/// The para id is already registered, or a registration is already in flight for it.
361		AlreadyRegistered,
362		/// There is no registration in flight for this para id.
363		NotPending,
364		/// The manager may not abandon this registration yet.
365		CannotCancelYet,
366		/// The head data is larger than the relay chain will accept.
367		HeadDataTooLarge,
368		/// The validation code is larger than the relay chain will accept.
369		CodeTooLarge,
370		/// The validation code is smaller than the relay chain will accept.
371		CodeTooSmall,
372		/// The message could not be handed to the transport.
373		SendFailed,
374		/// There are no more para ids to hand out.
375		NoFreeParaId,
376		/// The para is locked, so the manager may not act on it.
377		ParaLocked,
378		/// The para is already locked.
379		AlreadyLocked,
380		/// The para is not locked.
381		NotLocked,
382		/// The para is not registered on the relay chain.
383		NotRegistered,
384		/// The call is scaffolded but not implemented yet.
385		Unimplemented,
386	}
387
388	#[pallet::hooks]
389	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
390		fn integrity_test() {
391			// Otherwise no validation code could ever pass `register`.
392			assert!(
393				T::MinCodeSize::get() <= T::MaxCodeSize::get(),
394				"MinCodeSize ({}) must not exceed MaxCodeSize ({})",
395				T::MinCodeSize::get(),
396				T::MaxCodeSize::get(),
397			);
398		}
399	}
400
401	#[pallet::call]
402	impl<T: Config> Pallet<T> {
403		/// Accept a report from the relay chain's registrar pallet.
404		///
405		/// Not callable by users: the origin must be the relay chain.
406		#[pallet::call_index(0)]
407		#[pallet::weight(T::WeightInfo::receive())]
408		pub fn receive(origin: OriginFor<T>, message: MessageToPara) -> DispatchResult {
409			T::RelayOrigin::ensure_origin_or_root(origin)?;
410
411			match message {
412				MessageToPara::V1(MessageToParaV1::RegisterResponse {
413					para_id,
414					message_id,
415					outcome,
416				}) => Self::on_register_response(para_id, message_id, outcome),
417				MessageToPara::V1(MessageToParaV1::CancelResponse {
418					para_id,
419					message_id,
420					outcome,
421				}) => Self::on_cancel_response(para_id, message_id, outcome),
422				MessageToPara::V1(MessageToParaV1::DeregisterResponse {
423					para_id,
424					message_id,
425					outcome,
426				}) => Self::on_deregister_response(para_id, message_id, outcome),
427				MessageToPara::V1(MessageToParaV1::CodeUpgradeResponse {
428					para_id,
429					message_id,
430					outcome,
431				}) => Self::on_code_upgrade_response(para_id, message_id, outcome),
432				MessageToPara::V1(MessageToParaV1::CodeUpgradeScheduled {
433					para_id,
434					message_id,
435				}) => Self::on_code_upgrade_scheduled(para_id, message_id),
436				MessageToPara::V1(MessageToParaV1::SetHeadResponse {
437					para_id,
438					message_id,
439					outcome,
440				}) => Self::on_set_head_response(para_id, message_id, outcome),
441				MessageToPara::V1(MessageToParaV1::HeadNoted { para_id }) => {
442					Self::on_head_noted(para_id)
443				},
444			}
445		}
446
447		/// Reserve the next free para id for the caller.
448		///
449		/// Takes [`Config::ReservationConsideration`]. The caller becomes the manager of the new
450		/// id and is the only account that may [`Pallet::register`] against it.
451		#[pallet::call_index(1)]
452		#[pallet::weight(T::WeightInfo::reserve())]
453		pub fn reserve(origin: OriginFor<T>) -> DispatchResult {
454			let who = ensure_signed(origin)?;
455
456			let para_id = NextFreeParaId::<T>::get().max(T::FirstPublicParaId::get());
457			let next = para_id.checked_add(1).ok_or(Error::<T>::NoFreeParaId)?;
458			ensure!(!Paras::<T>::contains_key(para_id), Error::<T>::AlreadyRegistered);
459
460			let reservation = T::ReservationConsideration::new(&who, Footprint::from_parts(1, 0))?;
461
462			Paras::<T>::insert(
463				para_id,
464				ParaInfo {
465					manager: who.clone(),
466					reservation,
467					state: RegistrationState::Reserved,
468					locked: None,
469				},
470			);
471			NextFreeParaId::<T>::put(next);
472
473			Self::deposit_event(Event::Reserved { para_id, who });
474			Ok(())
475		}
476
477		/// Ask the relay chain to register head data and validation code for a reserved para id.
478		///
479		/// The validation code itself stays here: only `code_hash` and `code_len` are sent. The
480		/// caller uploads the blob to the relay chain separately, which accepts it only if it
481		/// hashes to `code_hash` and is exactly `code_len` bytes long.
482		///
483		/// ## Costs
484		///
485		/// Takes [`Config::RegistrationConsideration`] for the head data and the *declared* code
486		/// length, on top of the para id reservation. It is returned if the relay chain rejects
487		/// the registration or if the caller later abandons it.
488		#[pallet::call_index(2)]
489		#[pallet::weight(T::WeightInfo::register(genesis_head.len() as u32))]
490		pub fn register(
491			origin: OriginFor<T>,
492			para_id: ParaId,
493			genesis_head: Vec<u8>,
494			code_len: u32,
495			code_hash: H256,
496		) -> DispatchResult {
497			let who = ensure_signed(origin)?;
498
499			let mut info = Paras::<T>::get(para_id).ok_or(Error::<T>::NotReserved)?;
500			ensure!(info.manager == who, Error::<T>::NotOwner);
501			ensure!(
502				matches!(info.state, RegistrationState::Reserved),
503				Error::<T>::AlreadyRegistered
504			);
505
506			let head_len = genesis_head.len() as u32;
507			ensure!(head_len <= T::MaxHeadDataSize::get(), Error::<T>::HeadDataTooLarge);
508			ensure!(code_len >= T::MinCodeSize::get(), Error::<T>::CodeTooSmall);
509			ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
510
511			let ticket = T::RegistrationConsideration::new(
512				&who,
513				Self::registration_footprint(head_len, code_len),
514			)?;
515
516			let cancellable_at = T::BlockNumberProvider::current_block_number()
517				.saturating_add(T::PendingDeadline::get());
518			let message_id = Self::next_message_id();
519			info.state = RegistrationState::Pending { ticket, cancellable_at, message_id };
520			Paras::<T>::insert(para_id, info);
521
522			// A transport failure returns `Err` and unwinds everything above, ticket included.
523			T::SendToRelay::send(MessageToRelay::V1(MessageToRelayV1::Register {
524				para_id,
525				message_id,
526				manager: who.clone(),
527				genesis_head,
528				code_hash,
529				code_len,
530			}))
531			.map_err(|()| Error::<T>::SendFailed)?;
532
533			Self::deposit_event(Event::RegisterRequested { para_id, message_id, manager: who });
534			Ok(())
535		}
536
537		/// Give up on a registration the relay chain never reported on.
538		///
539		/// Callable from [`Config::PendingDeadline`] blocks after the request. Nothing on the
540		/// relay chain abandons a registration on its own, so this is what ends one whose code
541		/// never turned up, and the manager pays for the round trip rather than every relay-chain
542		/// block paying for a sweep.
543		///
544		/// The deposit is not released here: the relay chain is asked to drop the authorization
545		/// first, and [`Pallet::receive`] releases the deposit when it confirms. Waiting for that
546		/// answer is the point. A registration that did go through, with a verdict that got lost on
547		/// the way here, must not have its deposit refunded, and only the relay chain knows
548		/// which of the two happened.
549		///
550		/// The para id itself stays reserved either way, so the manager can simply try again.
551		#[pallet::call_index(3)]
552		#[pallet::weight(T::WeightInfo::cancel_registration())]
553		pub fn cancel_registration(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
554			let who = ensure_signed(origin)?;
555
556			let mut info = Paras::<T>::get(para_id).ok_or(Error::<T>::NotReserved)?;
557			ensure!(info.manager == who, Error::<T>::NotOwner);
558			let RegistrationState::Pending { ticket, cancellable_at, message_id: awaited } =
559				info.state
560			else {
561				return Err(Error::<T>::NotPending.into());
562			};
563			let now = T::BlockNumberProvider::current_block_number();
564			ensure!(now >= cancellable_at, Error::<T>::CannotCancelYet);
565
566			// Getting here means the relay chain's answer to `awaited` never turned up. That should
567			// not happen, so say so on chain instead of quietly retrying.
568			Self::report_unexpected(UnexpectedKind::ResponseNeverArrived {
569				para_id,
570				message_id: awaited,
571			});
572
573			// Another deadline's grace before the manager may ask again, so a request that goes
574			// missing can be retried without the relay chain being asked once per block.
575			let message_id = Self::next_message_id();
576			info.state = RegistrationState::Pending {
577				ticket,
578				cancellable_at: now.saturating_add(T::PendingDeadline::get()),
579				message_id,
580			};
581			Paras::<T>::insert(para_id, info);
582
583			// A transport failure returns `Err` and unwinds the new deadline with it.
584			T::SendToRelay::send(MessageToRelay::V1(MessageToRelayV1::CancelRegistration {
585				para_id,
586				message_id,
587			}))
588			.map_err(|()| Error::<T>::SendFailed)?;
589
590			Self::deposit_event(Event::CancelRequested { para_id, message_id, manager: who });
591			Ok(())
592		}
593
594		/// Lock a registered para, keeping the manager out of it.
595		#[pallet::call_index(4)]
596		#[pallet::weight(T::WeightInfo::add_lock())]
597		pub fn add_lock(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
598			let mut info = Paras::<T>::get(para_id).ok_or(Error::<T>::NotReserved)?;
599			Self::ensure_root_para_or_manager(origin, para_id, &info)?;
600			ensure!(!info.is_locked(), Error::<T>::AlreadyLocked);
601			ensure!(
602				matches!(info.state, RegistrationState::Registered { .. }),
603				Error::<T>::NotRegistered
604			);
605
606			info.locked = Some(true);
607			Paras::<T>::insert(para_id, info);
608
609			Self::deposit_event(Event::ParaLocked { para_id });
610			Ok(())
611		}
612
613		/// Unlock a para, handing control back to the manager.
614		///
615		/// The manager is not accepted here: a lock it could lift would not be a lock.
616		#[pallet::call_index(5)]
617		#[pallet::weight(T::WeightInfo::remove_lock())]
618		pub fn remove_lock(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
619			Self::ensure_root_or_para(origin, para_id)?;
620			let mut info = Paras::<T>::get(para_id).ok_or(Error::<T>::NotReserved)?;
621			ensure!(info.is_locked(), Error::<T>::NotLocked);
622
623			info.locked = Some(false);
624			Paras::<T>::insert(para_id, info);
625
626			Self::deposit_event(Event::ParaUnlocked { para_id });
627			Ok(())
628		}
629
630		#[pallet::call_index(6)]
631		#[pallet::weight(Weight::MAX)]
632		pub fn deregister(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
633			let _ = (origin, para_id);
634			// TODO(ahm-v2): request deregistration on the relay chain, reporting a retry from
635			// `Deregistering` as `UnexpectedKind::ResponseNeverArrived`.
636			Err(Error::<T>::Unimplemented.into())
637		}
638
639		#[pallet::call_index(7)]
640		#[pallet::weight(Weight::MAX)]
641		pub fn schedule_code_upgrade(
642			origin: OriginFor<T>,
643			para_id: ParaId,
644			code_hash: H256,
645			code_len: u32,
646		) -> DispatchResult {
647			let _ = (origin, para_id, code_hash, code_len);
648			// TODO(ahm-v2): send the code upgrade authorization to the relay chain.
649			Err(Error::<T>::Unimplemented.into())
650		}
651
652		#[pallet::call_index(8)]
653		#[pallet::weight(Weight::MAX)]
654		pub fn set_current_head(
655			origin: OriginFor<T>,
656			para_id: ParaId,
657			head: Vec<u8>,
658		) -> DispatchResult {
659			let _ = (origin, para_id, head);
660			// TODO(ahm-v2): send the new head to the relay chain.
661			Err(Error::<T>::Unimplemented.into())
662		}
663
664		#[pallet::call_index(9)]
665		#[pallet::weight(Weight::MAX)]
666		pub fn force_register(
667			origin: OriginFor<T>,
668			para_id: ParaId,
669			manager: T::AccountId,
670			genesis_head: Vec<u8>,
671			code_len: u32,
672			code_hash: H256,
673		) -> DispatchResult {
674			let _ = (origin, para_id, manager, genesis_head, code_len, code_hash);
675			// TODO(ahm-v2): send a root-authorized registration to the relay chain.
676			Err(Error::<T>::Unimplemented.into())
677		}
678	}
679}
680
681impl<T: Config> Pallet<T> {
682	/// The footprint a registration is charged for: the head data plus the *declared* code length.
683	pub fn registration_footprint(head_len: u32, code_len: u32) -> Footprint {
684		Footprint::from_parts(1, head_len.saturating_add(code_len) as usize)
685	}
686
687	/// Ensure `origin` may manage `para_id`: the para itself, its manager while unlocked, or root.
688	fn ensure_root_para_or_manager(
689		origin: frame_system::pallet_prelude::OriginFor<T>,
690		para_id: ParaId,
691		info: &ParaInfoOf<T>,
692	) -> DispatchResult {
693		if let Ok(id) = T::ParachainOrigin::ensure_origin(origin.clone()) {
694			ensure!(id == para_id, Error::<T>::NotOwner);
695			return Ok(());
696		}
697		if let Ok(who) = frame_system::ensure_signed(origin.clone()) {
698			ensure!(who == info.manager, Error::<T>::NotOwner);
699			ensure!(!info.is_locked(), Error::<T>::ParaLocked);
700			return Ok(());
701		}
702		frame_system::ensure_root(origin)?;
703		Ok(())
704	}
705
706	/// Ensure `origin` is root or `para_id` itself.
707	fn ensure_root_or_para(
708		origin: frame_system::pallet_prelude::OriginFor<T>,
709		para_id: ParaId,
710	) -> DispatchResult {
711		if frame_system::ensure_root(origin.clone()).is_ok() {
712			return Ok(());
713		}
714		let id = T::ParachainOrigin::ensure_origin(origin)?;
715		ensure!(id == para_id, Error::<T>::NotOwner);
716		Ok(())
717	}
718
719	/// Report a failed defensive check: loud in the logs, and visible on chain.
720	fn report_unexpected(kind: UnexpectedKind) {
721		log::error!(target: "runtime::registrar-para", "unexpected: {kind:?}");
722		Self::deposit_event(Event::Unexpected(kind));
723	}
724
725	/// Take the id for the next message to the relay chain.
726	fn next_message_id() -> u64 {
727		NextMessageId::<T>::mutate(|next| {
728			let id = *next;
729			*next = next.wrapping_add(1);
730			id
731		})
732	}
733
734	/// Apply the relay chain's verdict on a registration.
735	///
736	/// A response about a para id we are not expecting one for is dropped rather than treated as a
737	/// dispatch error: erroring here would unwind the whole incoming message for something we can
738	/// do nothing about anyway. They are reported as [`Event::Unexpected`] instead.
739	fn on_register_response(para_id: ParaId, message_id: u64, outcome: Outcome) -> DispatchResult {
740		let Some(mut info) = Paras::<T>::get(para_id) else {
741			Self::report_unexpected(UnexpectedKind::RegisterResponseForUnknownPara {
742				para_id,
743				message_id,
744			});
745			return Ok(());
746		};
747		let RegistrationState::Pending { ticket, message_id: expected, .. } = info.state else {
748			Self::report_unexpected(UnexpectedKind::RegisterResponseNotPending {
749				para_id,
750				message_id,
751			});
752			return Ok(());
753		};
754		if message_id != expected {
755			Self::report_unexpected(UnexpectedKind::StaleResponse {
756				para_id,
757				message_id,
758				expected,
759			});
760			return Ok(());
761		}
762
763		let manager = info.manager.clone();
764		match outcome {
765			Ok(()) => {
766				info.state = RegistrationState::Registered { ticket };
767				Paras::<T>::insert(para_id, info);
768				Self::deposit_event(Event::Registered { para_id, message_id, manager });
769			},
770			Err(reason) => {
771				ticket.drop(&info.manager)?;
772				info.state = RegistrationState::Reserved;
773				Paras::<T>::insert(para_id, info);
774				Self::deposit_event(Event::RegistrationFailed {
775					para_id,
776					message_id,
777					manager,
778					reason,
779				});
780			},
781		}
782
783		Ok(())
784	}
785
786	/// Lock a para the relay chain has seen produce a head.
787	fn on_head_noted(para_id: ParaId) -> DispatchResult {
788		let Some(mut info) = Paras::<T>::get(para_id) else {
789			Self::report_unexpected(UnexpectedKind::HeadNotedForUnknownPara { para_id });
790			return Ok(());
791		};
792
793		if info.locked.is_some() {
794			return Ok(());
795		}
796
797		if !matches!(info.state, RegistrationState::Registered { .. }) {
798			Self::report_unexpected(UnexpectedKind::HeadNotedForUnregisteredPara { para_id });
799		}
800
801		info.locked = Some(true);
802		Paras::<T>::insert(para_id, info);
803
804		Self::deposit_event(Event::ParaLocked { para_id });
805
806		Ok(())
807	}
808
809	/// Apply the relay chain's answer to a cancellation.
810	///
811	/// `Ok(())` means the authorization is gone, so the deposit goes back. The one refusal is
812	/// [`FailureReason::AlreadyRegistered`]: the code landed after all and the earlier verdict was
813	/// simply lost, so the para is recorded as registered and the deposit stays held.
814	///
815	/// Unlike a register response, an answer for a para that is no longer pending is expected
816	/// rather than unexpected: a verdict already in flight when the cancellation was sent settles
817	/// the registration first, and this then has nothing left to do.
818	fn on_cancel_response(para_id: ParaId, message_id: u64, outcome: Outcome) -> DispatchResult {
819		let Some(mut info) = Paras::<T>::get(para_id) else {
820			Self::report_unexpected(UnexpectedKind::CancelResponseForUnknownPara {
821				para_id,
822				message_id,
823			});
824			return Ok(());
825		};
826		let RegistrationState::Pending { ticket, message_id: expected, .. } = info.state else {
827			log::debug!(
828				target: "runtime::registrar-para",
829				"cancel response for para {para_id} which is no longer pending, dropping",
830			);
831			return Ok(());
832		};
833		if message_id != expected {
834			Self::report_unexpected(UnexpectedKind::StaleResponse {
835				para_id,
836				message_id,
837				expected,
838			});
839			return Ok(());
840		}
841
842		let manager = info.manager.clone();
843		match outcome {
844			Ok(()) => {
845				ticket.drop(&info.manager)?;
846				info.state = RegistrationState::Reserved;
847				Paras::<T>::insert(para_id, info);
848				Self::deposit_event(Event::RegistrationCancelled { para_id, message_id, manager });
849			},
850			Err(FailureReason::AlreadyRegistered) => {
851				info.state = RegistrationState::Registered { ticket };
852				Paras::<T>::insert(para_id, info);
853				Self::deposit_event(Event::Registered { para_id, message_id, manager });
854			},
855			// Nothing else is a cancellation the relay chain refuses, so leave the registration
856			// pending: the manager can ask again once the deadline comes round.
857			Err(reason) => {
858				Self::report_unexpected(UnexpectedKind::CancelRefused {
859					para_id,
860					message_id,
861					reason,
862				});
863			},
864		}
865
866		Ok(())
867	}
868
869	fn on_deregister_response(
870		para_id: ParaId,
871		message_id: u64,
872		outcome: Outcome,
873	) -> DispatchResult {
874		let _ = (para_id, message_id, outcome);
875		// TODO(ahm-v2): settle the pending deregistration from the relay chain's answer.
876		Err(Error::<T>::Unimplemented.into())
877	}
878
879	fn on_code_upgrade_response(
880		para_id: ParaId,
881		message_id: u64,
882		outcome: Result<u32, FailureReason>,
883	) -> DispatchResult {
884		let _ = (para_id, message_id, outcome);
885		// TODO(ahm-v2): settle the pending code upgrade from the relay chain's answer.
886		Err(Error::<T>::Unimplemented.into())
887	}
888
889	fn on_code_upgrade_scheduled(para_id: ParaId, message_id: u64) -> DispatchResult {
890		let _ = (para_id, message_id);
891		// TODO(ahm-v2): finish the code upgrade once the relay chain has scheduled it.
892		Err(Error::<T>::Unimplemented.into())
893	}
894
895	fn on_set_head_response(para_id: ParaId, message_id: u64, outcome: Outcome) -> DispatchResult {
896		let _ = (para_id, message_id, outcome);
897		// TODO(ahm-v2): settle the pending head update from the relay chain's answer.
898		Err(Error::<T>::Unimplemented.into())
899	}
900}