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

            
23
mod types;
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
134
        type RuntimeHoldReason: From<HoldReason>;
135

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

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

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

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

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

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

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

            
174
        type WeightInfo: WeightInfo;
175
    }
176

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

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

            
211
        UnknownProfileId,
212
        NextProfileIdShouldBeAvailable,
213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
555
15
            profile_owner_check(&profile)?;
556

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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