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
12910
#[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
781
        fn build(&self) {
104
781
            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
781
        }
123
    }
124

            
125
    /// Data preservers pallet.
126
46877
    #[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
15
        ProfileCreated {
183
            account: T::AccountId,
184
            profile_id: T::ProfileId,
185
            deposit: BalanceOf<T>,
186
        },
187
3
        ProfileUpdated {
188
            profile_id: T::ProfileId,
189
            old_deposit: BalanceOf<T>,
190
            new_deposit: BalanceOf<T>,
191
        },
192
2
        ProfileDeleted {
193
            profile_id: T::ProfileId,
194
            released_deposit: BalanceOf<T>,
195
        },
196
8
        AssignmentStarted {
197
            profile_id: T::ProfileId,
198
            para_id: ParaId,
199
        },
200
5
        AssignmentStopped {
201
            profile_id: T::ProfileId,
202
            para_id: ParaId,
203
        },
204
    }
205

            
206
80
    #[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
2022
    #[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
        pub fn create_profile(
255
            origin: OriginFor<T>,
256
            profile: Profile<T>,
257
155
        ) -> DispatchResultWithPostInfo {
258
155
            let account = ensure_signed(origin)?;
259

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

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

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

            
278
11
            let new_deposit = T::ProfileDeposit::compute_deposit(&profile)?;
279

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

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

            
301
8
                Ok(new_deposit)
302
11
            })
303
        }
304

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

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

            
319
7
                Ok(().into())
320
10
            })
321
        }
322

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

            
335
114
            Self::do_create_profile(profile, for_account, Zero::zero())
336
        }
337

            
338
        #[pallet::call_index(5)]
339
        #[pallet::weight(T::WeightInfo::force_update_profile(
340
            profile.url.len() as u32,
341
            profile.para_ids.len() as u32,
342
        ))]
343
        pub fn force_update_profile(
344
            origin: OriginFor<T>,
345
            profile_id: T::ProfileId,
346
            profile: Profile<T>,
347
8
        ) -> DispatchResultWithPostInfo {
348
8
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
349

            
350
7
            Self::do_update_profile(profile_id, profile, |existing_profile| {
351
7
                // We release the previous deposit
352
7
                T::Currency::release(
353
7
                    &HoldReason::ProfileDeposit.into(),
354
7
                    &existing_profile.account,
355
7
                    existing_profile.deposit,
356
7
                    Precision::Exact,
357
7
                )?;
358

            
359
                // New deposit is zero since its forced
360
7
                Ok(Zero::zero())
361
7
            })
362
        }
363

            
364
        #[pallet::call_index(6)]
365
        #[pallet::weight(T::WeightInfo::force_delete_profile())]
366
        pub fn force_delete_profile(
367
            origin: OriginFor<T>,
368
            profile_id: T::ProfileId,
369
9
        ) -> DispatchResultWithPostInfo {
370
9
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
371

            
372
8
            Self::do_delete_profile(profile_id, |_| Ok(().into()))
373
        }
374

            
375
        #[pallet::call_index(7)]
376
        #[pallet::weight(T::WeightInfo::start_assignment())]
377
        pub fn start_assignment(
378
            origin: OriginFor<T>,
379
            profile_id: T::ProfileId,
380
            para_id: ParaId,
381
            assigner_param: AssignerParameterOf<T>,
382
204
        ) -> DispatchResultWithPostInfo {
383
204
            let assigner = T::AssignmentOrigin::ensure_origin(origin, &para_id)?;
384

            
385
203
            Self::do_start_assignment(profile_id, para_id, |profile| {
386
200
                T::AssignmentProcessor::try_start_assignment(
387
200
                    assigner,
388
200
                    profile.account.clone(),
389
200
                    &profile.profile.assignment_request,
390
200
                    assigner_param,
391
200
                )
392
203
            })
393
        }
394

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

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

            
412
21
            match caller {
413
                // root or para id manager is allowed to call
414
19
                Either::Left(_) => (),
415
                // signed, must be profile owner
416
2
                Either::Right(account) => ensure!(
417
2
                    profile.account == account,
418
1
                    sp_runtime::DispatchError::BadOrigin
419
                ),
420
            }
421

            
422
20
            let Some((assignment_para_id, assignment_witness)) = profile.assignment.take() else {
423
1
                Err(Error::<T>::ProfileNotAssigned)?
424
            };
425

            
426
19
            if assignment_para_id != para_id {
427
1
                Err(Error::<T>::WrongParaId)?
428
18
            }
429

            
430
18
            T::AssignmentProcessor::try_stop_assignment(
431
18
                profile.account.clone(),
432
18
                assignment_witness,
433
18
            )?;
434

            
435
17
            Profiles::<T>::insert(profile_id, profile);
436
17

            
437
17
            {
438
17
                let mut assignments = Assignments::<T>::get(para_id);
439
17
                assignments.remove(&profile_id);
440
17
                Assignments::<T>::insert(para_id, assignments);
441
17
            }
442
17

            
443
17
            Self::deposit_event(Event::AssignmentStopped {
444
17
                profile_id,
445
17
                para_id,
446
17
            });
447
17

            
448
17
            Ok(().into())
449
        }
450

            
451
        #[pallet::call_index(9)]
452
        #[pallet::weight(T::WeightInfo::force_start_assignment())]
453
        pub fn force_start_assignment(
454
            origin: OriginFor<T>,
455
            profile_id: T::ProfileId,
456
            para_id: ParaId,
457
            assignment_witness: AssignmentWitnessOf<T>,
458
14
        ) -> DispatchResultWithPostInfo {
459
14
            ensure_root(origin)?;
460

            
461
14
            Self::do_start_assignment(profile_id, para_id, |_profile| Ok(assignment_witness))
462
        }
463
    }
464

            
465
    impl<T: Config> Pallet<T> {
466
268
        fn do_create_profile(
467
268
            profile: Profile<T>,
468
268
            account: T::AccountId,
469
268
            deposit: BalanceOf<T>,
470
268
        ) -> DispatchResultWithPostInfo {
471
268
            let id = NextProfileId::<T>::get();
472
268

            
473
268
            NextProfileId::<T>::set(
474
268
                id.checked_add(&One::one())
475
268
                    .ok_or(ArithmeticError::Overflow)?,
476
            );
477

            
478
268
            ensure!(
479
268
                !Profiles::<T>::contains_key(id),
480
1
                Error::<T>::NextProfileIdShouldBeAvailable
481
            );
482

            
483
267
            Profiles::<T>::insert(
484
267
                id,
485
267
                RegisteredProfile {
486
267
                    account: account.clone(),
487
267
                    deposit,
488
267
                    profile,
489
267
                    assignment: None,
490
267
                },
491
267
            );
492
267

            
493
267
            Self::deposit_event(Event::ProfileCreated {
494
267
                account,
495
267
                profile_id: id,
496
267
                deposit,
497
267
            });
498
267

            
499
267
            Ok(().into())
500
268
        }
501

            
502
18
        fn do_update_profile(
503
18
            profile_id: T::ProfileId,
504
18
            new_profile: Profile<T>,
505
18
            update_deposit: impl FnOnce(
506
18
                &RegisteredProfile<T>,
507
18
            ) -> Result<BalanceOf<T>, DispatchErrorWithPostInfo>,
508
18
        ) -> DispatchResultWithPostInfo {
509
18
            let Some(existing_profile) = Profiles::<T>::get(profile_id) else {
510
1
                Err(Error::<T>::UnknownProfileId)?
511
            };
512

            
513
17
            let new_deposit = update_deposit(&existing_profile)?;
514

            
515
15
            Profiles::<T>::insert(
516
15
                profile_id,
517
15
                RegisteredProfile {
518
15
                    deposit: new_deposit,
519
15
                    profile: new_profile,
520
15
                    ..existing_profile
521
15
                },
522
15
            );
523
15

            
524
15
            Self::deposit_event(Event::ProfileUpdated {
525
15
                profile_id,
526
15
                old_deposit: existing_profile.deposit,
527
15
                new_deposit,
528
15
            });
529
15

            
530
15
            Ok(().into())
531
18
        }
532

            
533
18
        fn do_delete_profile(
534
18
            profile_id: T::ProfileId,
535
18
            profile_owner_check: impl FnOnce(&RegisteredProfile<T>) -> DispatchResultWithPostInfo,
536
18
        ) -> DispatchResultWithPostInfo {
537
18
            let Some(profile) = Profiles::<T>::get(profile_id) else {
538
1
                Err(Error::<T>::UnknownProfileId)?
539
            };
540

            
541
17
            ensure!(
542
17
                profile.assignment.is_none(),
543
2
                Error::<T>::CantDeleteAssignedProfile,
544
            );
545

            
546
15
            profile_owner_check(&profile)?;
547

            
548
14
            T::Currency::release(
549
14
                &HoldReason::ProfileDeposit.into(),
550
14
                &profile.account,
551
14
                profile.deposit,
552
14
                Precision::Exact,
553
14
            )?;
554

            
555
14
            Profiles::<T>::remove(profile_id);
556
14

            
557
14
            Self::deposit_event(Event::ProfileDeleted {
558
14
                profile_id,
559
14
                released_deposit: profile.deposit,
560
14
            });
561
14

            
562
14
            Ok(().into())
563
18
        }
564

            
565
217
        fn do_start_assignment(
566
217
            profile_id: T::ProfileId,
567
217
            para_id: ParaId,
568
217
            witness_producer: impl FnOnce(
569
217
                &RegisteredProfile<T>,
570
217
            )
571
217
                -> Result<AssignmentWitnessOf<T>, DispatchErrorWithPostInfo>,
572
217
        ) -> DispatchResultWithPostInfo {
573
217
            let mut profile = Profiles::<T>::get(profile_id).ok_or(Error::<T>::UnknownProfileId)?;
574

            
575
216
            if profile.assignment.is_some() {
576
1
                Err(Error::<T>::ProfileAlreadyAssigned)?
577
215
            }
578

            
579
215
            if !profile.profile.para_ids.can_assign(&para_id) {
580
1
                Err(Error::<T>::ProfileIsNotElligibleForParaId)?
581
214
            }
582

            
583
            // Add profile id to BoundedVec early in case bound is reached
584
            {
585
214
                let mut assignments = Assignments::<T>::get(para_id);
586
214

            
587
214
                assignments
588
214
                    .try_insert(profile_id)
589
214
                    .map_err(|_| Error::<T>::MaxAssignmentsPerParaIdReached)?;
590

            
591
214
                Assignments::<T>::insert(para_id, assignments);
592
            }
593

            
594
214
            let witness = witness_producer(&profile)?;
595

            
596
208
            profile.assignment = Some((para_id, witness));
597
208
            Profiles::<T>::insert(profile_id, profile);
598
208

            
599
208
            Self::deposit_event(Event::AssignmentStarted {
600
208
                profile_id,
601
208
                para_id,
602
208
            });
603
208

            
604
208
            Ok(().into())
605
217
        }
606

            
607
223
        pub fn assignments_profiles(para_id: ParaId) -> impl Iterator<Item = Profile<T>> {
608
223
            Assignments::<T>::get(para_id)
609
223
                .into_iter()
610
223
                .filter_map(Profiles::<T>::get)
611
223
                .map(|profile| profile.profile)
612
223
        }
613

            
614
        /// Function that will be called when a container chain is deregistered. Cleans up all the
615
        /// storage related to this para_id.
616
        /// Cannot fail.
617
95
        pub fn para_deregistered(para_id: ParaId) {
618
95
            Assignments::<T>::remove(para_id);
619
95
        }
620

            
621
223
        pub fn check_valid_for_collating(para_id: ParaId) -> DispatchResult {
622
223
            if !Self::assignments_profiles(para_id)
623
223
                .any(|profile| profile.mode == ProfileMode::Bootnode)
624
            {
625
4
                Err(Error::<T>::NoBootNodes)?
626
219
            }
627

            
628
219
            Ok(())
629
223
        }
630
    }
631
}