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

            
63
29328
#[frame_support::pallet]
64
pub mod pallet {
65
    use super::*;
66

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

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

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

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

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

            
102
    #[pallet::genesis_build]
103
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
104
781
        fn build(&self) {
105
781
            for (para_id, profile_owner, url, request, witness) in self.bootnodes.clone() {
106
                let profile = Profile {
107
                    url: url.try_into().expect("should fit in BoundedVec"),
108
                    para_ids: ParaIdsFilter::Whitelist({
109
                        let mut set = BoundedBTreeSet::new();
110
                        set.try_insert(para_id).expect("to fit in BoundedBTreeSet");
111
                        set
112
                    }),
113
                    mode: ProfileMode::Bootnode,
114
                    assignment_request: request,
115
                };
116

            
117
                let profile_id = NextProfileId::<T>::get();
118
                Pallet::<T>::do_create_profile(profile, profile_owner, Zero::zero())
119
                    .expect("to create profile");
120
                Pallet::<T>::do_start_assignment(profile_id, para_id, |_| Ok(witness))
121
                    .expect("to start assignment");
122
            }
123
781
        }
124
    }
125

            
126
    /// Data preservers pallet.
127
69439
    #[pallet::pallet]
128
    pub struct Pallet<T>(PhantomData<T>);
129

            
130
    #[pallet::config]
131
    pub trait Config: frame_system::Config {
132
        /// Overarching event type.
133
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
134

            
135
        type RuntimeHoldReason: From<HoldReason>;
136

            
137
        type Currency: Inspect<Self::AccountId>
138
            + Balanced<Self::AccountId>
139
            + MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
140

            
141
        type ProfileId: Default
142
            + FullCodec
143
            + TypeInfo
144
            + Copy
145
            + Clone
146
            + Debug
147
            + Eq
148
            + CheckedAdd
149
            + One
150
            + Ord
151
            + MaxEncodedLen;
152

            
153
        // Who can call start_assignment/stop_assignment?
154
        type AssignmentOrigin: EnsureOriginWithArg<
155
            Self::RuntimeOrigin,
156
            ParaId,
157
            Success = Self::AccountId,
158
        >;
159

            
160
        // Who can call force_X?
161
        type ForceSetProfileOrigin: EnsureOrigin<Self::RuntimeOrigin>;
162

            
163
        #[pallet::constant]
164
        type MaxAssignmentsPerParaId: Get<u32> + Clone;
165
        #[pallet::constant]
166
        type MaxNodeUrlLen: Get<u32> + Clone;
167
        #[pallet::constant]
168
        type MaxParaIdsVecLen: Get<u32> + Clone;
169

            
170
        /// How much must be deposited to register a profile.
171
        type ProfileDeposit: StorageDeposit<Profile<Self>, BalanceOf<Self>>;
172

            
173
        type AssignmentProcessor: AssignmentProcessor<Self::AccountId>;
174

            
175
        type WeightInfo: WeightInfo;
176
    }
177

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

            
207
624
    #[pallet::error]
208
    pub enum Error<T> {
209
        /// This container chain does not have any boot nodes
210
        NoBootNodes,
211

            
212
        UnknownProfileId,
213
        NextProfileIdShouldBeAvailable,
214

            
215
        /// Made for `AssignmentProcessor` implementors to report a mismatch between
216
        /// `ProviderRequest` and `AssignerParameter`.
217
        AssignmentPaymentRequestParameterMismatch,
218

            
219
        ProfileAlreadyAssigned,
220
        ProfileNotAssigned,
221
        ProfileIsNotElligibleForParaId,
222
        WrongParaId,
223
        MaxAssignmentsPerParaIdReached,
224
        CantDeleteAssignedProfile,
225
    }
226

            
227
    #[pallet::composite_enum]
228
    pub enum HoldReason {
229
275
        ProfileDeposit,
230
    }
231

            
232
1936
    #[pallet::storage]
233
    pub type Profiles<T: Config> =
234
        StorageMap<_, Blake2_128Concat, T::ProfileId, RegisteredProfile<T>, OptionQuery>;
235

            
236
1920
    #[pallet::storage]
237
    pub type NextProfileId<T: Config> = StorageValue<_, T::ProfileId, ValueQuery>;
238

            
239
1527
    #[pallet::storage]
240
    pub type Assignments<T: Config> = StorageMap<
241
        _,
242
        Blake2_128Concat,
243
        ParaId,
244
        BoundedBTreeSet<T::ProfileId, T::MaxAssignmentsPerParaId>,
245
        ValueQuery,
246
    >;
247

            
248
624
    #[pallet::call]
249
    impl<T: Config> Pallet<T> {
250
        #[pallet::call_index(1)]
251
        #[pallet::weight(T::WeightInfo::create_profile(
252
            profile.url.len() as u32,
253
            profile.para_ids.len() as u32,
254
        ))]
255
        #[allow(clippy::useless_conversion)]
256
        pub fn create_profile(
257
            origin: OriginFor<T>,
258
            profile: Profile<T>,
259
155
        ) -> DispatchResultWithPostInfo {
260
155
            let account = ensure_signed(origin)?;
261

            
262
155
            let deposit = T::ProfileDeposit::compute_deposit(&profile)?;
263
155
            T::Currency::hold(&HoldReason::ProfileDeposit.into(), &account, deposit)?;
264

            
265
154
            Self::do_create_profile(profile, account, deposit)
266
        }
267

            
268
        #[pallet::call_index(2)]
269
        #[pallet::weight(T::WeightInfo::update_profile(
270
            profile.url.len() as u32,
271
            profile.para_ids.len() as u32,
272
        ))]
273
        #[allow(clippy::useless_conversion)]
274
        pub fn update_profile(
275
            origin: OriginFor<T>,
276
            profile_id: T::ProfileId,
277
            profile: Profile<T>,
278
11
        ) -> DispatchResultWithPostInfo {
279
11
            let account = ensure_signed(origin)?;
280

            
281
11
            let new_deposit = T::ProfileDeposit::compute_deposit(&profile)?;
282

            
283
11
            Self::do_update_profile(profile_id, profile, |existing_profile| {
284
10
                ensure!(
285
10
                    existing_profile.account == account,
286
1
                    sp_runtime::DispatchError::BadOrigin,
287
                );
288

            
289
9
                if let Some(diff) = new_deposit.checked_sub(&existing_profile.deposit) {
290
8
                    T::Currency::hold(
291
8
                        &HoldReason::ProfileDeposit.into(),
292
8
                        &existing_profile.account,
293
8
                        diff,
294
8
                    )?;
295
1
                } else if let Some(diff) = existing_profile.deposit.checked_sub(&new_deposit) {
296
1
                    T::Currency::release(
297
1
                        &HoldReason::ProfileDeposit.into(),
298
1
                        &existing_profile.account,
299
1
                        diff,
300
1
                        Precision::Exact,
301
1
                    )?;
302
                }
303

            
304
8
                Ok(new_deposit)
305
11
            })
306
        }
307

            
308
        #[pallet::call_index(3)]
309
        #[pallet::weight(T::WeightInfo::delete_profile())]
310
        #[allow(clippy::useless_conversion)]
311
        pub fn delete_profile(
312
            origin: OriginFor<T>,
313
            profile_id: T::ProfileId,
314
10
        ) -> DispatchResultWithPostInfo {
315
10
            let account = ensure_signed(origin)?;
316

            
317
10
            Self::do_delete_profile(profile_id, |profile| {
318
8
                ensure!(
319
8
                    profile.account == account,
320
1
                    sp_runtime::DispatchError::BadOrigin,
321
                );
322

            
323
7
                Ok(().into())
324
10
            })
325
        }
326

            
327
        #[pallet::call_index(4)]
328
        #[pallet::weight(T::WeightInfo::force_create_profile(
329
            profile.url.len() as u32,
330
            profile.para_ids.len() as u32,
331
        ))]
332
        #[allow(clippy::useless_conversion)]
333
        pub fn force_create_profile(
334
            origin: OriginFor<T>,
335
            profile: Profile<T>,
336
            for_account: T::AccountId,
337
115
        ) -> DispatchResultWithPostInfo {
338
115
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
339

            
340
114
            Self::do_create_profile(profile, for_account, Zero::zero())
341
        }
342

            
343
        #[pallet::call_index(5)]
344
        #[pallet::weight(T::WeightInfo::force_update_profile(
345
            profile.url.len() as u32,
346
            profile.para_ids.len() as u32,
347
        ))]
348
        #[allow(clippy::useless_conversion)]
349
        pub fn force_update_profile(
350
            origin: OriginFor<T>,
351
            profile_id: T::ProfileId,
352
            profile: Profile<T>,
353
8
        ) -> DispatchResultWithPostInfo {
354
8
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
355

            
356
7
            Self::do_update_profile(profile_id, profile, |existing_profile| {
357
7
                // We release the previous deposit
358
7
                T::Currency::release(
359
7
                    &HoldReason::ProfileDeposit.into(),
360
7
                    &existing_profile.account,
361
7
                    existing_profile.deposit,
362
7
                    Precision::Exact,
363
7
                )?;
364

            
365
                // New deposit is zero since its forced
366
7
                Ok(Zero::zero())
367
7
            })
368
        }
369

            
370
        #[pallet::call_index(6)]
371
        #[pallet::weight(T::WeightInfo::force_delete_profile())]
372
        #[allow(clippy::useless_conversion)]
373
        pub fn force_delete_profile(
374
            origin: OriginFor<T>,
375
            profile_id: T::ProfileId,
376
9
        ) -> DispatchResultWithPostInfo {
377
9
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
378

            
379
8
            Self::do_delete_profile(profile_id, |_| Ok(().into()))
380
        }
381

            
382
        #[pallet::call_index(7)]
383
        #[pallet::weight(T::WeightInfo::start_assignment())]
384
        #[allow(clippy::useless_conversion)]
385
        pub fn start_assignment(
386
            origin: OriginFor<T>,
387
            profile_id: T::ProfileId,
388
            para_id: ParaId,
389
            assigner_param: AssignerParameterOf<T>,
390
204
        ) -> DispatchResultWithPostInfo {
391
204
            let assigner = T::AssignmentOrigin::ensure_origin(origin, &para_id)?;
392

            
393
203
            Self::do_start_assignment(profile_id, para_id, |profile| {
394
200
                T::AssignmentProcessor::try_start_assignment(
395
200
                    assigner,
396
200
                    profile.account.clone(),
397
200
                    &profile.profile.assignment_request,
398
200
                    assigner_param,
399
200
                )
400
203
            })
401
        }
402

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

            
419
22
            let mut profile = Profiles::<T>::get(profile_id).ok_or(Error::<T>::UnknownProfileId)?;
420

            
421
21
            match caller {
422
                // root or para id manager is allowed to call
423
19
                Either::Left(_) => (),
424
                // signed, must be profile owner
425
2
                Either::Right(account) => ensure!(
426
2
                    profile.account == account,
427
1
                    sp_runtime::DispatchError::BadOrigin
428
                ),
429
            }
430

            
431
20
            let Some((assignment_para_id, assignment_witness)) = profile.assignment.take() else {
432
1
                Err(Error::<T>::ProfileNotAssigned)?
433
            };
434

            
435
19
            if assignment_para_id != para_id {
436
1
                Err(Error::<T>::WrongParaId)?
437
18
            }
438

            
439
18
            T::AssignmentProcessor::try_stop_assignment(
440
18
                profile.account.clone(),
441
18
                assignment_witness,
442
18
            )?;
443

            
444
17
            Profiles::<T>::insert(profile_id, profile);
445
17

            
446
17
            {
447
17
                let mut assignments = Assignments::<T>::get(para_id);
448
17
                assignments.remove(&profile_id);
449
17
                Assignments::<T>::insert(para_id, assignments);
450
17
            }
451
17

            
452
17
            Self::deposit_event(Event::AssignmentStopped {
453
17
                profile_id,
454
17
                para_id,
455
17
            });
456
17

            
457
17
            Ok(().into())
458
        }
459

            
460
        #[pallet::call_index(9)]
461
        #[pallet::weight(T::WeightInfo::force_start_assignment())]
462
        #[allow(clippy::useless_conversion)]
463
        pub fn force_start_assignment(
464
            origin: OriginFor<T>,
465
            profile_id: T::ProfileId,
466
            para_id: ParaId,
467
            assignment_witness: AssignmentWitnessOf<T>,
468
14
        ) -> DispatchResultWithPostInfo {
469
14
            ensure_root(origin)?;
470

            
471
14
            Self::do_start_assignment(profile_id, para_id, |_profile| Ok(assignment_witness))
472
        }
473
    }
474

            
475
    impl<T: Config> Pallet<T> {
476
268
        fn do_create_profile(
477
268
            profile: Profile<T>,
478
268
            account: T::AccountId,
479
268
            deposit: BalanceOf<T>,
480
268
        ) -> DispatchResultWithPostInfo {
481
268
            let id = NextProfileId::<T>::get();
482
268

            
483
268
            NextProfileId::<T>::set(
484
268
                id.checked_add(&One::one())
485
268
                    .ok_or(ArithmeticError::Overflow)?,
486
            );
487

            
488
268
            ensure!(
489
268
                !Profiles::<T>::contains_key(id),
490
1
                Error::<T>::NextProfileIdShouldBeAvailable
491
            );
492

            
493
267
            Profiles::<T>::insert(
494
267
                id,
495
267
                RegisteredProfile {
496
267
                    account: account.clone(),
497
267
                    deposit,
498
267
                    profile,
499
267
                    assignment: None,
500
267
                },
501
267
            );
502
267

            
503
267
            Self::deposit_event(Event::ProfileCreated {
504
267
                account,
505
267
                profile_id: id,
506
267
                deposit,
507
267
            });
508
267

            
509
267
            Ok(().into())
510
268
        }
511

            
512
18
        fn do_update_profile(
513
18
            profile_id: T::ProfileId,
514
18
            new_profile: Profile<T>,
515
18
            update_deposit: impl FnOnce(
516
18
                &RegisteredProfile<T>,
517
18
            ) -> Result<BalanceOf<T>, DispatchErrorWithPostInfo>,
518
18
        ) -> DispatchResultWithPostInfo {
519
18
            let Some(existing_profile) = Profiles::<T>::get(profile_id) else {
520
1
                Err(Error::<T>::UnknownProfileId)?
521
            };
522

            
523
17
            let new_deposit = update_deposit(&existing_profile)?;
524

            
525
15
            Profiles::<T>::insert(
526
15
                profile_id,
527
15
                RegisteredProfile {
528
15
                    deposit: new_deposit,
529
15
                    profile: new_profile,
530
15
                    ..existing_profile
531
15
                },
532
15
            );
533
15

            
534
15
            Self::deposit_event(Event::ProfileUpdated {
535
15
                profile_id,
536
15
                old_deposit: existing_profile.deposit,
537
15
                new_deposit,
538
15
            });
539
15

            
540
15
            Ok(().into())
541
18
        }
542

            
543
18
        fn do_delete_profile(
544
18
            profile_id: T::ProfileId,
545
18
            profile_owner_check: impl FnOnce(&RegisteredProfile<T>) -> DispatchResultWithPostInfo,
546
18
        ) -> DispatchResultWithPostInfo {
547
18
            let Some(profile) = Profiles::<T>::get(profile_id) else {
548
1
                Err(Error::<T>::UnknownProfileId)?
549
            };
550

            
551
17
            ensure!(
552
17
                profile.assignment.is_none(),
553
2
                Error::<T>::CantDeleteAssignedProfile,
554
            );
555

            
556
15
            profile_owner_check(&profile)?;
557

            
558
14
            T::Currency::release(
559
14
                &HoldReason::ProfileDeposit.into(),
560
14
                &profile.account,
561
14
                profile.deposit,
562
14
                Precision::Exact,
563
14
            )?;
564

            
565
14
            Profiles::<T>::remove(profile_id);
566
14

            
567
14
            Self::deposit_event(Event::ProfileDeleted {
568
14
                profile_id,
569
14
                released_deposit: profile.deposit,
570
14
            });
571
14

            
572
14
            Ok(().into())
573
18
        }
574

            
575
217
        fn do_start_assignment(
576
217
            profile_id: T::ProfileId,
577
217
            para_id: ParaId,
578
217
            witness_producer: impl FnOnce(
579
217
                &RegisteredProfile<T>,
580
217
            )
581
217
                -> Result<AssignmentWitnessOf<T>, DispatchErrorWithPostInfo>,
582
217
        ) -> DispatchResultWithPostInfo {
583
217
            let mut profile = Profiles::<T>::get(profile_id).ok_or(Error::<T>::UnknownProfileId)?;
584

            
585
216
            if profile.assignment.is_some() {
586
1
                Err(Error::<T>::ProfileAlreadyAssigned)?
587
215
            }
588

            
589
215
            if !profile.profile.para_ids.can_assign(&para_id) {
590
1
                Err(Error::<T>::ProfileIsNotElligibleForParaId)?
591
214
            }
592

            
593
            // Add profile id to BoundedVec early in case bound is reached
594
            {
595
214
                let mut assignments = Assignments::<T>::get(para_id);
596
214

            
597
214
                assignments
598
214
                    .try_insert(profile_id)
599
214
                    .map_err(|_| Error::<T>::MaxAssignmentsPerParaIdReached)?;
600

            
601
214
                Assignments::<T>::insert(para_id, assignments);
602
            }
603

            
604
214
            let witness = witness_producer(&profile)?;
605

            
606
208
            profile.assignment = Some((para_id, witness));
607
208
            Profiles::<T>::insert(profile_id, profile);
608
208

            
609
208
            Self::deposit_event(Event::AssignmentStarted {
610
208
                profile_id,
611
208
                para_id,
612
208
            });
613
208

            
614
208
            Ok(().into())
615
217
        }
616

            
617
223
        pub fn assignments_profiles(para_id: ParaId) -> impl Iterator<Item = Profile<T>> {
618
223
            Assignments::<T>::get(para_id)
619
223
                .into_iter()
620
223
                .filter_map(Profiles::<T>::get)
621
223
                .map(|profile| profile.profile)
622
223
        }
623

            
624
        /// Function that will be called when a container chain is deregistered. Cleans up all the
625
        /// storage related to this para_id.
626
        /// Cannot fail.
627
95
        pub fn para_deregistered(para_id: ParaId) {
628
95
            Assignments::<T>::remove(para_id);
629
95
        }
630

            
631
223
        pub fn check_valid_for_collating(para_id: ParaId) -> DispatchResult {
632
223
            if !Self::assignments_profiles(para_id)
633
223
                .any(|profile| profile.mode == ProfileMode::Bootnode)
634
            {
635
4
                Err(Error::<T>::NoBootNodes)?
636
219
            }
637

            
638
219
            Ok(())
639
223
        }
640
    }
641
}