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
12188
#[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>::AssignmentPayment as AssignmentPayment<
71
        <T as frame_system::Config>::AccountId,
72
    >>::ProviderRequest;
73

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

            
78
    pub type AssignmentWitnessOf<T> = <<T as Config>::AssignmentPayment as AssignmentPayment<
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
4
        fn build(&self) {
104
4
            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
4
        }
123
    }
124

            
125
    /// Data preservers pallet.
126
41956
    #[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 AssignmentPayment: AssignmentPayment<Self::AccountId>;
173

            
174
        type WeightInfo: WeightInfo;
175
    }
176

            
177
    #[pallet::event]
178
430
    #[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
14
        ProfileCreated {
183
            account: T::AccountId,
184
            profile_id: T::ProfileId,
185
            deposit: BalanceOf<T>,
186
        },
187
2
        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
52
    #[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 `AssignmentPayment` 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
231
        ProfileDeposit,
229
    }
230

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

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

            
238
648
    #[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
1398
    #[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
130
        ) -> DispatchResultWithPostInfo {
258
130
            let account = ensure_signed(origin)?;
259

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

            
263
129
            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
10
        ) -> DispatchResultWithPostInfo {
276
10
            let account = ensure_signed(origin)?;
277

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

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

            
286
8
                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
                } else if let Some(diff) = existing_profile.deposit.checked_sub(&new_deposit) {
293
                    T::Currency::release(
294
                        &HoldReason::ProfileDeposit.into(),
295
                        &existing_profile.account,
296
                        diff,
297
                        Precision::Exact,
298
                    )?;
299
                }
300

            
301
7
                Ok(new_deposit)
302
10
            })
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
92
        ) -> DispatchResultWithPostInfo {
333
92
            T::ForceSetProfileOrigin::ensure_origin(origin)?;
334

            
335
91
            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
156
        ) -> DispatchResultWithPostInfo {
383
156
            let assigner = T::AssignmentOrigin::ensure_origin(origin, &para_id)?;
384

            
385
155
            Self::do_start_assignment(profile_id, para_id, |profile| {
386
154
                T::AssignmentPayment::try_start_assignment(
387
154
                    assigner,
388
154
                    profile.account.clone(),
389
154
                    &profile.profile.assignment_request,
390
154
                    assigner_param,
391
154
                )
392
155
            })
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
21
        ) -> DispatchResultWithPostInfo {
402
21
            let caller = EitherOfDiverse::<
403
21
                // root or para manager can call without being the owner
404
21
                EitherOfDiverse<T::AssignmentOrigin, EnsureRoot<T::AccountId>>,
405
21
                // otherwise it can be a simple signed account but it will require
406
21
                // cheking if it is the owner of the profile
407
21
                EnsureSigned<T::AccountId>,
408
21
            >::ensure_origin(origin, &para_id)?;
409

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

            
412
20
            match caller {
413
                // root or para id manager is allowed to call
414
19
                Either::Left(_) => (),
415
                // signed, must be profile owner
416
1
                Either::Right(account) => ensure!(
417
1
                    profile.account == account,
418
                    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::AssignmentPayment::try_stop_assignment(profile.account.clone(), assignment_witness)?;
431

            
432
17
            Profiles::<T>::insert(profile_id, profile);
433
17

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

            
440
17
            Self::deposit_event(Event::AssignmentStopped {
441
17
                profile_id,
442
17
                para_id,
443
17
            });
444
17

            
445
17
            Ok(().into())
446
        }
447

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

            
458
14
            Self::do_start_assignment(profile_id, para_id, |_profile| Ok(assignment_witness))
459
        }
460
    }
461

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

            
470
220
            NextProfileId::<T>::set(
471
220
                id.checked_add(&One::one())
472
220
                    .ok_or(ArithmeticError::Overflow)?,
473
            );
474

            
475
220
            ensure!(
476
220
                !Profiles::<T>::contains_key(id),
477
1
                Error::<T>::NextProfileIdShouldBeAvailable
478
            );
479

            
480
219
            Profiles::<T>::insert(
481
219
                id,
482
219
                RegisteredProfile {
483
219
                    account: account.clone(),
484
219
                    deposit,
485
219
                    profile,
486
219
                    assignment: None,
487
219
                },
488
219
            );
489
219

            
490
219
            Self::deposit_event(Event::ProfileCreated {
491
219
                account,
492
219
                profile_id: id,
493
219
                deposit,
494
219
            });
495
219

            
496
219
            Ok(().into())
497
220
        }
498

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

            
510
16
            let new_deposit = update_deposit(&existing_profile)?;
511

            
512
14
            Profiles::<T>::insert(
513
14
                profile_id,
514
14
                RegisteredProfile {
515
14
                    deposit: new_deposit,
516
14
                    profile: new_profile,
517
14
                    ..existing_profile
518
14
                },
519
14
            );
520
14

            
521
14
            Self::deposit_event(Event::ProfileUpdated {
522
14
                profile_id,
523
14
                old_deposit: existing_profile.deposit,
524
14
                new_deposit,
525
14
            });
526
14

            
527
14
            Ok(().into())
528
17
        }
529

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

            
538
17
            ensure!(
539
17
                profile.assignment.is_none(),
540
2
                Error::<T>::CantDeleteAssignedProfile,
541
            );
542

            
543
15
            profile_owner_check(&profile)?;
544

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

            
552
14
            Profiles::<T>::remove(profile_id);
553
14

            
554
14
            Self::deposit_event(Event::ProfileDeleted {
555
14
                profile_id,
556
14
                released_deposit: profile.deposit,
557
14
            });
558
14

            
559
14
            Ok(().into())
560
18
        }
561

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

            
572
168
            if profile.assignment.is_some() {
573
                Err(Error::<T>::ProfileAlreadyAssigned)?
574
168
            }
575

            
576
168
            if !profile.profile.para_ids.can_assign(&para_id) {
577
                Err(Error::<T>::ProfileIsNotElligibleForParaId)?
578
168
            }
579

            
580
            // Add profile id to BoundedVec early in case bound is reached
581
            {
582
168
                let mut assignments = Assignments::<T>::get(para_id);
583
168

            
584
168
                assignments
585
168
                    .try_insert(profile_id)
586
168
                    .map_err(|_| Error::<T>::MaxAssignmentsPerParaIdReached)?;
587

            
588
168
                Assignments::<T>::insert(para_id, assignments);
589
            }
590

            
591
168
            let witness = witness_producer(&profile)?;
592

            
593
166
            profile.assignment = Some((para_id, witness));
594
166
            Profiles::<T>::insert(profile_id, profile);
595
166

            
596
166
            Self::deposit_event(Event::AssignmentStarted {
597
166
                profile_id,
598
166
                para_id,
599
166
            });
600
166

            
601
166
            Ok(().into())
602
169
        }
603

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

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

            
618
115
        pub fn check_valid_for_collating(para_id: ParaId) -> DispatchResult {
619
115
            if !Self::assignments_profiles(para_id)
620
115
                .any(|profile| profile.mode == ProfileMode::Bootnode)
621
            {
622
3
                Err(Error::<T>::NoBootNodes)?
623
112
            }
624

            
625
112
            Ok(())
626
115
        }
627
    }
628
}