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}