1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
//! # Data Preservers Pallet
18
//!
19
//! This pallet allows container chains to select data preservers.
20

            
21
#![cfg_attr(not(feature = "std"), no_std)]
22
extern crate alloc;
23

            
24
mod types;
25

            
26
pub use {pallet::*, types::*};
27

            
28
#[cfg(test)]
29
mod mock;
30

            
31
#[cfg(test)]
32
mod tests;
33

            
34
#[cfg(any(test, feature = "runtime-benchmarks"))]
35
mod benchmarks;
36

            
37
pub mod weights;
38
pub use weights::WeightInfo;
39

            
40
pub mod migrations;
41

            
42
use {
43
    alloc::vec::Vec,
44
    core::fmt::Debug,
45
    dp_core::ParaId,
46
    frame_support::{
47
        dispatch::DispatchErrorWithPostInfo,
48
        pallet_prelude::*,
49
        traits::{
50
            fungible::{Balanced, Inspect, MutateHold},
51
            tokens::Precision,
52
            EitherOfDiverse, EnsureOriginWithArg,
53
        },
54
        DefaultNoBound,
55
    },
56
    frame_system::{pallet_prelude::*, EnsureRoot, EnsureSigned},
57
    parity_scale_codec::FullCodec,
58
    sp_runtime::{
59
        traits::{CheckedAdd, CheckedSub, Get, One, Zero},
60
        ArithmeticError, Either,
61
    },
62
    tp_traits::StorageDeposit,
63
};
64

            
65
#[frame_support::pallet]
66
pub mod pallet {
67
    use super::*;
68

            
69
    /// Balance used by this pallet
70
    pub type BalanceOf<T> =
71
        <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
72

            
73
    pub type ProviderRequestOf<T> = <<T as Config>::AssignmentProcessor as AssignmentProcessor<
74
        <T as frame_system::Config>::AccountId,
75
    >>::ProviderRequest;
76

            
77
    pub type AssignerParameterOf<T> = <<T as Config>::AssignmentProcessor as AssignmentProcessor<
78
        <T as frame_system::Config>::AccountId,
79
    >>::AssignerParameter;
80

            
81
    pub type AssignmentWitnessOf<T> = <<T as Config>::AssignmentProcessor as AssignmentProcessor<
82
        <T as frame_system::Config>::AccountId,
83
    >>::AssignmentWitness;
84

            
85
    #[pallet::genesis_config]
86
    #[derive(DefaultNoBound)]
87
    pub struct GenesisConfig<T: Config> {
88
        pub bootnodes: Vec<(
89
            // ParaId the profile will be assigned to
90
            ParaId,
91
            // Owner of the profile
92
            T::AccountId,
93
            // URL of the bootnode
94
            Vec<u8>,
95
            // Assignment request
96
            ProviderRequestOf<T>,
97
            // Assignment witness (try_start_assignment is skipped)
98
            AssignmentWitnessOf<T>,
99
        )>,
100
        #[serde(skip)]
101
        pub _phantom: PhantomData<T>,
102
    }
103

            
104
    #[pallet::genesis_build]
105
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
106
1217
        fn build(&self) {
107
1217
            for (para_id, profile_owner, url, request, witness) in self.bootnodes.clone() {
108
                let bootnode_url = Some(url.try_into().expect("should fit in BoundedVec"));
109

            
110
                let profile = Profile {
111
                    direct_rpc_urls: Default::default(),
112
                    proxy_rpc_urls: Default::default(),
113
                    bootnode_url,
114
                    para_ids: ParaIdsFilter::Whitelist({
115
                        let mut set = BoundedBTreeSet::new();
116
                        set.try_insert(para_id).expect("to fit in BoundedBTreeSet");
117
                        set
118
                    }),
119
                    node_type: NodeType::Substrate,
120
                    assignment_request: request,
121
                    additional_info: Default::default(),
122
                };
123

            
124
                let profile_id = NextProfileId::<T>::get();
125
                Pallet::<T>::do_create_profile(profile, profile_owner, Zero::zero())
126
                    .expect("to create profile");
127
                Pallet::<T>::do_start_assignment(profile_id, para_id, |_| Ok(witness))
128
                    .expect("to start assignment");
129
            }
130
1217
        }
131
    }
132

            
133
    /// Data preservers pallet.
134
    #[pallet::pallet]
135
    pub struct Pallet<T>(PhantomData<T>);
136

            
137
    #[pallet::config]
138
    pub trait Config: frame_system::Config {
139
        type RuntimeHoldReason: From<HoldReason>;
140

            
141
        type Currency: Inspect<Self::AccountId>
142
            + Balanced<Self::AccountId>
143
            + MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
144

            
145
        type ProfileId: Default
146
            + FullCodec
147
            + TypeInfo
148
            + Copy
149
            + Clone
150
            + Debug
151
            + Eq
152
            + CheckedAdd
153
            + One
154
            + Ord
155
            + MaxEncodedLen;
156

            
157
        // Who can call start_assignment/stop_assignment?
158
        type AssignmentOrigin: EnsureOriginWithArg<
159
            Self::RuntimeOrigin,
160
            ParaId,
161
            Success = Self::AccountId,
162
        >;
163

            
164
        // Who can call force_X?
165
        type ForceSetProfileOrigin: EnsureOrigin<Self::RuntimeOrigin>;
166

            
167
        #[pallet::constant]
168
        type MaxAssignmentsPerParaId: Get<u32> + Clone;
169
        #[pallet::constant]
170
        type MaxNodeUrlCount: Get<u32> + Clone;
171
        #[pallet::constant]
172
        type MaxStringLen: Get<u32> + Clone;
173
        #[pallet::constant]
174
        type MaxParaIdsVecLen: Get<u32> + Clone;
175

            
176
        /// How much must be deposited to register a profile.
177
        type ProfileDeposit: StorageDeposit<Profile<Self>, BalanceOf<Self>>;
178

            
179
        type AssignmentProcessor: AssignmentProcessor<Self::AccountId>;
180

            
181
        type WeightInfo: WeightInfo;
182
    }
183

            
184
    #[pallet::event]
185
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
186
    pub enum Event<T: Config> {
187
        /// The list of boot_nodes changed.
188
        BootNodesChanged { para_id: ParaId },
189
        ProfileCreated {
190
            account: T::AccountId,
191
            profile_id: T::ProfileId,
192
            deposit: BalanceOf<T>,
193
        },
194
        ProfileUpdated {
195
            profile_id: T::ProfileId,
196
            old_deposit: BalanceOf<T>,
197
            new_deposit: BalanceOf<T>,
198
        },
199
        ProfileDeleted {
200
            profile_id: T::ProfileId,
201
            released_deposit: BalanceOf<T>,
202
        },
203
        AssignmentStarted {
204
            profile_id: T::ProfileId,
205
            para_id: ParaId,
206
        },
207
        AssignmentStopped {
208
            profile_id: T::ProfileId,
209
            para_id: ParaId,
210
        },
211
    }
212

            
213
    #[pallet::error]
214
    pub enum Error<T> {
215
        /// This container chain does not have any boot nodes
216
        NoBootNodes,
217

            
218
        UnknownProfileId,
219
        NextProfileIdShouldBeAvailable,
220

            
221
        /// Made for `AssignmentProcessor` implementors to report a mismatch between
222
        /// `ProviderRequest` and `AssignerParameter`.
223
        AssignmentPaymentRequestParameterMismatch,
224

            
225
        ProfileAlreadyAssigned,
226
        ProfileNotAssigned,
227
        ProfileIsNotElligibleForParaId,
228
        WrongParaId,
229
        MaxAssignmentsPerParaIdReached,
230
        CantDeleteAssignedProfile,
231
    }
232

            
233
    #[pallet::composite_enum]
234
    pub enum HoldReason {
235
        ProfileDeposit,
236
    }
237

            
238
    #[pallet::storage]
239
    pub type Profiles<T: Config> =
240
        StorageMap<_, Blake2_128Concat, T::ProfileId, RegisteredProfile<T>, OptionQuery>;
241

            
242
    #[pallet::storage]
243
    pub type NextProfileId<T: Config> = StorageValue<_, T::ProfileId, ValueQuery>;
244

            
245
    #[pallet::storage]
246
    pub type Assignments<T: Config> = StorageMap<
247
        _,
248
        Blake2_128Concat,
249
        ParaId,
250
        BoundedBTreeSet<T::ProfileId, T::MaxAssignmentsPerParaId>,
251
        ValueQuery,
252
    >;
253

            
254
    #[pallet::call]
255
    impl<T: Config> Pallet<T> {
256
        #[pallet::call_index(1)]
257
        #[pallet::weight(T::WeightInfo::create_profile(
258
            profile.strings_len() as u32,
259
            profile.para_ids.len() as u32,
260
        ))]
261
        #[allow(clippy::useless_conversion)]
262
        pub fn create_profile(
263
            origin: OriginFor<T>,
264
            profile: Profile<T>,
265
54
        ) -> DispatchResultWithPostInfo {
266
54
            let account = ensure_signed(origin)?;
267

            
268
54
            let deposit = T::ProfileDeposit::compute_deposit(&profile)?;
269
54
            T::Currency::hold(&HoldReason::ProfileDeposit.into(), &account, deposit)?;
270

            
271
53
            Self::do_create_profile(profile, account, deposit)
272
        }
273

            
274
        #[pallet::call_index(2)]
275
        #[pallet::weight(T::WeightInfo::update_profile(
276
            profile.strings_len() as u32,
277
            profile.para_ids.len() as u32,
278
        ))]
279
        #[allow(clippy::useless_conversion)]
280
        pub fn update_profile(
281
            origin: OriginFor<T>,
282
            profile_id: T::ProfileId,
283
            profile: Profile<T>,
284
5
        ) -> DispatchResultWithPostInfo {
285
5
            let account = ensure_signed(origin)?;
286

            
287
5
            let new_deposit = T::ProfileDeposit::compute_deposit(&profile)?;
288

            
289
5
            Self::do_update_profile(profile_id, profile, |existing_profile| {
290
4
                ensure!(
291
4
                    existing_profile.account == account,
292
1
                    sp_runtime::DispatchError::BadOrigin,
293
                );
294

            
295
3
                if let Some(diff) = new_deposit.checked_sub(&existing_profile.deposit) {
296
2
                    T::Currency::hold(
297
2
                        &HoldReason::ProfileDeposit.into(),
298
2
                        &existing_profile.account,
299
2
                        diff,
300
1
                    )?;
301
1
                } else if let Some(diff) = existing_profile.deposit.checked_sub(&new_deposit) {
302
1
                    T::Currency::release(
303
1
                        &HoldReason::ProfileDeposit.into(),
304
1
                        &existing_profile.account,
305
1
                        diff,
306
1
                        Precision::Exact,
307
                    )?;
308
                }
309

            
310
2
                Ok(new_deposit)
311
4
            })
312
        }
313

            
314
        #[pallet::call_index(3)]
315
        #[pallet::weight(T::WeightInfo::delete_profile())]
316
        #[allow(clippy::useless_conversion)]
317
        pub fn delete_profile(
318
            origin: OriginFor<T>,
319
            profile_id: T::ProfileId,
320
4
        ) -> DispatchResultWithPostInfo {
321
4
            let account = ensure_signed(origin)?;
322

            
323
4
            Self::do_delete_profile(profile_id, |profile| {
324
2
                ensure!(
325
2
                    profile.account == account,
326
1
                    sp_runtime::DispatchError::BadOrigin,
327
                );
328

            
329
1
                Ok(().into())
330
2
            })
331
        }
332

            
333
        #[pallet::call_index(4)]
334
        #[pallet::weight(T::WeightInfo::force_create_profile(
335
            profile.strings_len() as u32,
336
            profile.para_ids.len() as u32,
337
        ))]
338
        #[allow(clippy::useless_conversion)]
339
        pub fn force_create_profile(
340
            origin: OriginFor<T>,
341
            profile: Profile<T>,
342
            for_account: T::AccountId,
343
134
        ) -> DispatchResultWithPostInfo {
344
134
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
345

            
346
133
            Self::do_create_profile(profile, for_account, Zero::zero())
347
        }
348

            
349
        #[pallet::call_index(5)]
350
        #[pallet::weight(T::WeightInfo::force_update_profile(
351
            profile.strings_len() as u32,
352
            profile.para_ids.len() as u32,
353
        ))]
354
        #[allow(clippy::useless_conversion)]
355
        pub fn force_update_profile(
356
            origin: OriginFor<T>,
357
            profile_id: T::ProfileId,
358
            profile: Profile<T>,
359
2
        ) -> DispatchResultWithPostInfo {
360
2
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
361

            
362
1
            Self::do_update_profile(profile_id, profile, |existing_profile| {
363
                // We release the previous deposit
364
1
                T::Currency::release(
365
1
                    &HoldReason::ProfileDeposit.into(),
366
1
                    &existing_profile.account,
367
1
                    existing_profile.deposit,
368
1
                    Precision::Exact,
369
                )?;
370

            
371
                // New deposit is zero since its forced
372
1
                Ok(Zero::zero())
373
1
            })
374
        }
375

            
376
        #[pallet::call_index(6)]
377
        #[pallet::weight(T::WeightInfo::force_delete_profile())]
378
        #[allow(clippy::useless_conversion)]
379
        pub fn force_delete_profile(
380
            origin: OriginFor<T>,
381
            profile_id: T::ProfileId,
382
3
        ) -> DispatchResultWithPostInfo {
383
3
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
384

            
385
2
            Self::do_delete_profile(profile_id, |_| Ok(().into()))
386
        }
387

            
388
        #[pallet::call_index(7)]
389
        #[pallet::weight(T::WeightInfo::start_assignment())]
390
        #[allow(clippy::useless_conversion)]
391
        pub fn start_assignment(
392
            origin: OriginFor<T>,
393
            profile_id: T::ProfileId,
394
            para_id: ParaId,
395
            assigner_param: AssignerParameterOf<T>,
396
166
        ) -> DispatchResultWithPostInfo {
397
166
            let assigner = T::AssignmentOrigin::ensure_origin(origin, &para_id)?;
398

            
399
165
            Self::do_start_assignment(profile_id, para_id, |profile| {
400
162
                T::AssignmentProcessor::try_start_assignment(
401
162
                    assigner,
402
162
                    profile.account.clone(),
403
162
                    &profile.profile.assignment_request,
404
162
                    assigner_param,
405
                )
406
162
            })
407
        }
408

            
409
        #[pallet::call_index(8)]
410
        #[pallet::weight(T::WeightInfo::stop_assignment())]
411
        #[allow(clippy::useless_conversion)]
412
        pub fn stop_assignment(
413
            origin: OriginFor<T>,
414
            profile_id: T::ProfileId,
415
            para_id: ParaId,
416
10
        ) -> DispatchResultWithPostInfo {
417
10
            let caller = EitherOfDiverse::<
418
10
                // root or para manager can call without being the owner
419
10
                EitherOfDiverse<T::AssignmentOrigin, EnsureRoot<T::AccountId>>,
420
10
                // otherwise it can be a simple signed account but it will require
421
10
                // cheking if it is the owner of the profile
422
10
                EnsureSigned<T::AccountId>,
423
10
            >::ensure_origin(origin, &para_id)?;
424

            
425
10
            let mut profile = Profiles::<T>::get(profile_id).ok_or(Error::<T>::UnknownProfileId)?;
426

            
427
9
            match caller {
428
                // root or para id manager is allowed to call
429
7
                Either::Left(_) => (),
430
                // signed, must be profile owner
431
2
                Either::Right(account) => ensure!(
432
2
                    profile.account == account,
433
1
                    sp_runtime::DispatchError::BadOrigin
434
                ),
435
            }
436

            
437
8
            let Some((assignment_para_id, assignment_witness)) = profile.assignment.take() else {
438
1
                Err(Error::<T>::ProfileNotAssigned)?
439
            };
440

            
441
7
            if assignment_para_id != para_id {
442
1
                Err(Error::<T>::WrongParaId)?
443
6
            }
444

            
445
6
            T::AssignmentProcessor::try_stop_assignment(
446
6
                profile.account.clone(),
447
6
                assignment_witness,
448
1
            )?;
449

            
450
5
            Profiles::<T>::insert(profile_id, profile);
451

            
452
5
            {
453
5
                let mut assignments = Assignments::<T>::get(para_id);
454
5
                assignments.remove(&profile_id);
455
5
                Assignments::<T>::insert(para_id, assignments);
456
5
            }
457

            
458
5
            Self::deposit_event(Event::AssignmentStopped {
459
5
                profile_id,
460
5
                para_id,
461
5
            });
462

            
463
5
            Ok(().into())
464
        }
465

            
466
        #[pallet::call_index(9)]
467
        #[pallet::weight(T::WeightInfo::force_start_assignment())]
468
        #[allow(clippy::useless_conversion)]
469
        pub fn force_start_assignment(
470
            origin: OriginFor<T>,
471
            profile_id: T::ProfileId,
472
            para_id: ParaId,
473
            assignment_witness: AssignmentWitnessOf<T>,
474
        ) -> DispatchResultWithPostInfo {
475
            ensure_root(origin)?;
476

            
477
            Self::do_start_assignment(profile_id, para_id, |_profile| Ok(assignment_witness))
478
        }
479

            
480
        #[pallet::call_index(10)]
481
        #[pallet::weight(T::WeightInfo::poke_deposit())]
482
        #[allow(clippy::useless_conversion)]
483
        pub fn poke_deposit(
484
            origin: OriginFor<T>,
485
            profile_id: T::ProfileId,
486
8
        ) -> DispatchResultWithPostInfo {
487
8
            let who = ensure_signed(origin)?;
488

            
489
8
            let Some(mut reg) = Profiles::<T>::get(profile_id) else {
490
1
                Err(Error::<T>::UnknownProfileId)?
491
            };
492

            
493
            // Only the owner can call
494
7
            ensure!(reg.account == who, sp_runtime::DispatchError::BadOrigin);
495

            
496
6
            let required = T::ProfileDeposit::compute_deposit(&reg.profile)?;
497
6
            let current = reg.deposit;
498

            
499
            // If the deposit is already correct, do nothing
500
6
            if required == current {
501
1
                return Ok(().into());
502
5
            }
503

            
504
            // Adjust the hold as necessary
505
5
            if let Some(delta) = required.checked_sub(&current) {
506
                // Increase the hold
507
4
                T::Currency::hold(&HoldReason::ProfileDeposit.into(), &reg.account, delta)?;
508
1
            } else if let Some(delta) = current.checked_sub(&required) {
509
                // Release excess
510
1
                T::Currency::release(
511
1
                    &HoldReason::ProfileDeposit.into(),
512
1
                    &reg.account,
513
1
                    delta,
514
1
                    Precision::Exact,
515
                )?;
516
            }
517

            
518
4
            reg.deposit = required;
519
4
            Profiles::<T>::insert(profile_id, reg);
520

            
521
4
            Self::deposit_event(Event::ProfileUpdated {
522
4
                profile_id,
523
4
                old_deposit: current,
524
4
                new_deposit: required,
525
4
            });
526

            
527
4
            Ok(().into())
528
        }
529
    }
530

            
531
    impl<T: Config> Pallet<T> {
532
186
        fn do_create_profile(
533
186
            profile: Profile<T>,
534
186
            account: T::AccountId,
535
186
            deposit: BalanceOf<T>,
536
186
        ) -> DispatchResultWithPostInfo {
537
186
            let id = NextProfileId::<T>::get();
538

            
539
186
            NextProfileId::<T>::set(
540
186
                id.checked_add(&One::one())
541
186
                    .ok_or(ArithmeticError::Overflow)?,
542
            );
543

            
544
186
            ensure!(
545
186
                !Profiles::<T>::contains_key(id),
546
1
                Error::<T>::NextProfileIdShouldBeAvailable
547
            );
548

            
549
185
            Profiles::<T>::insert(
550
185
                id,
551
185
                RegisteredProfile {
552
185
                    account: account.clone(),
553
185
                    deposit,
554
185
                    profile,
555
185
                    assignment: None,
556
185
                },
557
            );
558

            
559
185
            Self::deposit_event(Event::ProfileCreated {
560
185
                account,
561
185
                profile_id: id,
562
185
                deposit,
563
185
            });
564

            
565
185
            Ok(().into())
566
186
        }
567

            
568
6
        fn do_update_profile(
569
6
            profile_id: T::ProfileId,
570
6
            new_profile: Profile<T>,
571
6
            update_deposit: impl FnOnce(
572
6
                &RegisteredProfile<T>,
573
6
            ) -> Result<BalanceOf<T>, DispatchErrorWithPostInfo>,
574
6
        ) -> DispatchResultWithPostInfo {
575
6
            let Some(existing_profile) = Profiles::<T>::get(profile_id) else {
576
1
                Err(Error::<T>::UnknownProfileId)?
577
            };
578

            
579
5
            let new_deposit = update_deposit(&existing_profile)?;
580

            
581
3
            Profiles::<T>::insert(
582
3
                profile_id,
583
3
                RegisteredProfile {
584
3
                    deposit: new_deposit,
585
3
                    profile: new_profile,
586
3
                    ..existing_profile
587
3
                },
588
            );
589

            
590
3
            Self::deposit_event(Event::ProfileUpdated {
591
3
                profile_id,
592
3
                old_deposit: existing_profile.deposit,
593
3
                new_deposit,
594
3
            });
595

            
596
3
            Ok(().into())
597
6
        }
598

            
599
6
        fn do_delete_profile(
600
6
            profile_id: T::ProfileId,
601
6
            profile_owner_check: impl FnOnce(&RegisteredProfile<T>) -> DispatchResultWithPostInfo,
602
6
        ) -> DispatchResultWithPostInfo {
603
6
            let Some(profile) = Profiles::<T>::get(profile_id) else {
604
1
                Err(Error::<T>::UnknownProfileId)?
605
            };
606

            
607
5
            ensure!(
608
5
                profile.assignment.is_none(),
609
2
                Error::<T>::CantDeleteAssignedProfile,
610
            );
611

            
612
3
            profile_owner_check(&profile)?;
613

            
614
2
            T::Currency::release(
615
2
                &HoldReason::ProfileDeposit.into(),
616
2
                &profile.account,
617
2
                profile.deposit,
618
2
                Precision::Exact,
619
            )?;
620

            
621
2
            Profiles::<T>::remove(profile_id);
622

            
623
2
            Self::deposit_event(Event::ProfileDeleted {
624
2
                profile_id,
625
2
                released_deposit: profile.deposit,
626
2
            });
627

            
628
2
            Ok(().into())
629
6
        }
630

            
631
165
        fn do_start_assignment(
632
165
            profile_id: T::ProfileId,
633
165
            para_id: ParaId,
634
165
            witness_producer: impl FnOnce(
635
165
                &RegisteredProfile<T>,
636
165
            )
637
165
                -> Result<AssignmentWitnessOf<T>, DispatchErrorWithPostInfo>,
638
165
        ) -> DispatchResultWithPostInfo {
639
165
            let mut profile = Profiles::<T>::get(profile_id).ok_or(Error::<T>::UnknownProfileId)?;
640

            
641
164
            if profile.assignment.is_some() {
642
1
                Err(Error::<T>::ProfileAlreadyAssigned)?
643
163
            }
644

            
645
163
            if !profile.profile.para_ids.can_assign(&para_id) {
646
1
                Err(Error::<T>::ProfileIsNotElligibleForParaId)?
647
162
            }
648

            
649
            // Add profile id to BoundedVec early in case bound is reached
650
            {
651
162
                let mut assignments = Assignments::<T>::get(para_id);
652

            
653
162
                assignments
654
162
                    .try_insert(profile_id)
655
162
                    .map_err(|_| Error::<T>::MaxAssignmentsPerParaIdReached)?;
656

            
657
162
                Assignments::<T>::insert(para_id, assignments);
658
            }
659

            
660
162
            let witness = witness_producer(&profile)?;
661

            
662
156
            profile.assignment = Some((para_id, witness));
663
156
            Profiles::<T>::insert(profile_id, profile);
664

            
665
156
            Self::deposit_event(Event::AssignmentStarted {
666
156
                profile_id,
667
156
                para_id,
668
156
            });
669

            
670
156
            Ok(().into())
671
165
        }
672

            
673
409
        pub fn assignments_profiles(para_id: ParaId) -> impl Iterator<Item = Profile<T>> {
674
409
            Assignments::<T>::get(para_id)
675
409
                .into_iter()
676
409
                .filter_map(Profiles::<T>::get)
677
409
                .map(|profile| profile.profile)
678
409
        }
679

            
680
        /// Function that will be called when a container chain is deregistered. Cleans up all the
681
        /// storage related to this para_id.
682
        /// Cannot fail.
683
83
        pub fn para_deregistered(para_id: ParaId) {
684
83
            Assignments::<T>::remove(para_id);
685
83
        }
686

            
687
409
        pub fn check_valid_for_collating(para_id: ParaId) -> DispatchResult {
688
409
            if !Self::assignments_profiles(para_id).any(|profile| profile.bootnode_url.is_some()) {
689
13
                Err(Error::<T>::NoBootNodes)?
690
396
            }
691

            
692
396
            Ok(())
693
409
        }
694
    }
695
}