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
//! # XCM Core Buyer Pallet
18
//!
19
//! This pallet allows collators to buy parathread cores on demand.
20

            
21
#![cfg_attr(not(feature = "std"), no_std)]
22

            
23
use frame_support::{Deserialize, Serialize};
24
pub use pallet::*;
25

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

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

            
32
#[cfg(any(test, feature = "runtime-benchmarks"))]
33
mod benchmarks;
34
pub mod weights;
35
pub use weights::WeightInfo;
36

            
37
use tp_traits::{AuthorNotingHook, BlockNumber, SlotFrequency};
38

            
39
use {
40
    dp_core::ParaId,
41
    frame_support::{
42
        dispatch::GetDispatchInfo,
43
        pallet_prelude::*,
44
        traits::fungible::{Balanced, Inspect},
45
    },
46
    frame_system::pallet_prelude::*,
47
    parity_scale_codec::EncodeLike,
48
    sp_consensus_aura::Slot,
49
    sp_runtime::traits::{AccountIdConversion, Convert, Get},
50
    sp_std::{vec, vec::Vec},
51
    staging_xcm::{
52
        latest::{Asset, Assets, InteriorLocation, Response, Xcm},
53
        prelude::*,
54
    },
55
    tp_traits::{LatestAuthorInfoFetcher, ParathreadParams},
56
    tp_xcm_core_buyer::BuyCoreCollatorProof,
57
};
58

            
59
pub trait XCMNotifier<T: Config> {
60
    fn new_notify_query(
61
        responder: impl Into<Location>,
62
        notify: impl Into<<T as Config>::RuntimeCall>,
63
        timeout: BlockNumberFor<T>,
64
        match_querier: impl Into<Location>,
65
    ) -> u64;
66
}
67

            
68
/// Dummy implementation. Should only be used for testing.
69
impl<T: Config> XCMNotifier<T> for () {
70
20
    fn new_notify_query(
71
20
        _responder: impl Into<Location>,
72
20
        _notify: impl Into<<T as Config>::RuntimeCall>,
73
20
        _timeout: BlockNumberFor<T>,
74
20
        _match_querier: impl Into<Location>,
75
20
    ) -> u64 {
76
20
        0
77
20
    }
78
}
79

            
80
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
81
704
#[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo)]
82
pub struct InFlightCoreBuyingOrder<BN> {
83
    para_id: ParaId,
84
    query_id: QueryId,
85
    ttl: BN,
86
}
87

            
88
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, scale_info::TypeInfo)]
89
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
90
pub enum BuyingError<BlockNumber> {
91
    OrderAlreadyExists {
92
        ttl: BlockNumber,
93
        current_block_number: BlockNumber,
94
    },
95
    BlockProductionPending {
96
        ttl: BlockNumber,
97
        current_block_number: BlockNumber,
98
    },
99
    NotAParathread,
100
    NotAllowedToProduceBlockRightNow {
101
        slot_frequency: SlotFrequency,
102
        max_slot_earlier_core_buying_permitted: Slot,
103
        last_block_production_slot: Slot,
104
    },
105
}
106

            
107
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
108
6564
    fn on_container_author_noted(
109
6564
        _author: &T::AccountId,
110
6564
        _block_number: BlockNumber,
111
6564
        para_id: ParaId,
112
6564
    ) -> Weight {
113
6564
        PendingBlocks::<T>::remove(para_id);
114
6564

            
115
6564
        T::DbWeight::get().writes(1)
116
6564
    }
117
}
118

            
119
8922
#[frame_support::pallet]
120
pub mod pallet {
121
    use {
122
        super::*, nimbus_primitives::SlotBeacon, pallet_xcm::ensure_response,
123
        sp_runtime::RuntimeAppPublic,
124
    };
125

            
126
13616
    #[pallet::pallet]
127
    #[pallet::without_storage_info]
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
        type Currency: Inspect<Self::AccountId> + Balanced<Self::AccountId>;
135

            
136
        type XcmSender: SendXcm;
137
        /// Get encoded call to buy a core in the relay chain. This will be passed to the XCM
138
        /// `Transact` instruction.
139
        type GetPurchaseCoreCall: GetPurchaseCoreCall<Self::RelayChain>;
140
        /// How to convert a `ParaId` into an `AccountId32`. Used to derive the parathread tank
141
        /// account in `interior_multilocation`.
142
        type GetParathreadAccountId: Convert<ParaId, [u8; 32]>;
143
        /// The max price that the parathread is willing to pay for a core, in relay chain currency.
144
        /// If `None`, defaults to `u128::MAX`, the parathread will pay the market price with no
145
        /// upper bound.
146
        type GetParathreadMaxCorePrice: GetParathreadMaxCorePrice;
147
        /// Orchestartor chain `ParaId`. Used in `absolute_multilocation` to convert the
148
        /// `interior_multilocation` into what the relay chain needs to allow to `DepositAsset`.
149
        type SelfParaId: Get<ParaId>;
150
        type RelayChain: Default
151
            + Encode
152
            + Decode
153
            + TypeInfo
154
            + EncodeLike
155
            + Clone
156
            + PartialEq
157
            + sp_std::fmt::Debug;
158

            
159
        /// Get the parathread params. Used to verify that the para id is a parathread.
160
        // TODO: and in the future to restrict the ability to buy a core depending on slot frequency
161
        type GetParathreadParams: GetParathreadParams;
162
        /// Validate if particular account id and public key pair belongs to a collator and the collator
163
        /// is selected to collate for particular para id.
164
        type CheckCollatorValidity: CheckCollatorValidity<Self::AccountId, Self::CollatorPublicKey>;
165
        /// A configuration for base priority of unsigned transactions.
166
        ///
167
        /// This is exposed so that it can be tuned for particular runtime, when
168
        /// multiple pallets send unsigned transactions.
169
        #[pallet::constant]
170
        type UnsignedPriority: Get<TransactionPriority>;
171

            
172
        /// TTL for pending blocks entry, which prevents anyone to submit another core buying xcm.
173
        #[pallet::constant]
174
        type PendingBlocksTtl: Get<BlockNumberFor<Self>>;
175

            
176
        /// TTL to be used in xcm's notify query
177
        #[pallet::constant]
178
        type CoreBuyingXCMQueryTtl: Get<BlockNumberFor<Self>>;
179

            
180
        /// Additional ttl for in flight orders (total would be CoreBuyingXCMQueryTtl + AdditionalTtlForInflightOrders)
181
        /// after which the in flight orders can be cleaned up by anyone.
182
        #[pallet::constant]
183
        type AdditionalTtlForInflightOrders: Get<BlockNumberFor<Self>>;
184

            
185
        #[pallet::constant]
186
        type UniversalLocation: Get<InteriorLocation>;
187

            
188
        type RuntimeOrigin: Into<Result<pallet_xcm::Origin, <Self as Config>::RuntimeOrigin>>
189
            + From<<Self as frame_system::Config>::RuntimeOrigin>;
190

            
191
        /// The overarching call type
192
        type RuntimeCall: From<Call<Self>> + Encode + GetDispatchInfo;
193

            
194
        /// Outcome notifier implements functionality to enable reporting back the outcome
195
        type XCMNotifier: XCMNotifier<Self>;
196

            
197
        type LatestAuthorInfoFetcher: LatestAuthorInfoFetcher<Self::AccountId>;
198

            
199
        type SlotBeacon: SlotBeacon;
200

            
201
        /// A PublicKey can be converted into an `AccountId`. This is required in order to verify
202
        /// the collator signature
203
        type CollatorPublicKey: Member
204
            + Parameter
205
            + RuntimeAppPublic
206
            + MaybeSerializeDeserialize
207
            + MaxEncodedLen;
208

            
209
        type WeightInfo: WeightInfo;
210
    }
211

            
212
    #[pallet::event]
213
66
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
214
    pub enum Event<T: Config> {
215
50
        /// An XCM message to buy a core for this parathread has been sent to the relay chain.
216
        BuyCoreXcmSent {
217
            para_id: ParaId,
218
            transaction_status_query_id: QueryId,
219
        },
220
21
        /// We received response for xcm
221
        ReceivedBuyCoreXCMResult { para_id: ParaId, response: Response },
222

            
223
2
        /// We cleaned up expired pending blocks entries.
224
        CleanedUpExpiredPendingBlocksEntries { para_ids: Vec<ParaId> },
225

            
226
2
        /// We cleaned up expired in flight orders entries.
227
        CleanedUpExpiredInFlightOrderEntries { para_ids: Vec<ParaId> },
228
    }
229

            
230
22
    #[pallet::error]
231
    pub enum Error<T> {
232
        InvalidProof,
233
        ErrorValidatingXCM,
234
        ErrorDeliveringXCM,
235
        /// An order for this para id already exists
236
        OrderAlreadyExists,
237
        /// The para id is not a parathread
238
        NotAParathread,
239
        /// There are too many in-flight orders, buying cores will not work until some of those
240
        /// orders finish.
241
        InFlightLimitReached,
242
        /// There are no collators assigned to this parathread, so no point in buying a core
243
        NoAssignedCollators,
244
        /// This collator is not assigned to this parathread
245
        CollatorNotAssigned,
246
        /// The `XcmWeights` storage has not been set. This must have been set by root with the
247
        /// value of the relay chain xcm call weight and extrinsic weight
248
        XcmWeightStorageNotSet,
249
        /// Converting a multilocation into a relay relative multilocation failed
250
        ReanchorFailed,
251
        /// Inverting location from destination point of view failed
252
        LocationInversionFailed,
253
        /// Modifying XCM to report the result of XCM failed
254
        ReportNotifyingSetupFailed,
255
        /// Unexpected XCM response
256
        UnexpectedXCMResponse,
257
        /// Block production is pending for para id with successfully placed order
258
        BlockProductionPending,
259
        /// Block production is not allowed for current slot
260
        NotAllowedToProduceBlockRightNow,
261
        /// Collator signature nonce is incorrect
262
        IncorrectCollatorSignatureNonce,
263
        /// Collator signature is invalid
264
        InvalidCollatorSignature,
265
    }
266

            
267
    impl<T: Config> From<BuyingError<BlockNumberFor<T>>> for Error<T> {
268
10
        fn from(value: BuyingError<BlockNumberFor<T>>) -> Self {
269
10
            match value {
270
5
                BuyingError::OrderAlreadyExists { .. } => Error::<T>::OrderAlreadyExists,
271
2
                BuyingError::BlockProductionPending { .. } => Error::<T>::BlockProductionPending,
272
1
                BuyingError::NotAParathread => Error::<T>::NotAParathread,
273
                BuyingError::NotAllowedToProduceBlockRightNow { .. } => {
274
2
                    Error::<T>::NotAllowedToProduceBlockRightNow
275
                }
276
            }
277
10
        }
278
    }
279

            
280
    /// Set of parathreads that have already sent an XCM message to buy a core recently.
281
    /// Used to avoid 2 collators buying a core at the same time, because it is only possible to buy
282
    /// 1 core in 1 relay block for the same parathread.
283
165
    #[pallet::storage]
284
    pub type InFlightOrders<T: Config> =
285
        StorageMap<_, Twox128, ParaId, InFlightCoreBuyingOrder<BlockNumberFor<T>>, OptionQuery>;
286

            
287
    /// Number of pending blocks
288
6668
    #[pallet::storage]
289
    pub type PendingBlocks<T: Config> =
290
        StorageMap<_, Twox128, ParaId, BlockNumberFor<T>, OptionQuery>;
291

            
292
    /// Mapping of QueryId to ParaId
293
111
    #[pallet::storage]
294
    pub type QueryIdToParaId<T: Config> = StorageMap<_, Twox128, QueryId, ParaId, OptionQuery>;
295

            
296
    /// This must be set by root with the value of the relay chain xcm call weight and extrinsic
297
    /// weight limit. This is a storage item because relay chain weights can change, so we need to
298
    /// be able to adjust them without doing a runtime upgrade.
299
138
    #[pallet::storage]
300
    pub type RelayXcmWeightConfig<T: Config> =
301
        StorageValue<_, RelayXcmWeightConfigInner<T>, OptionQuery>;
302

            
303
    /// Collator signature nonce for reply protection
304
74
    #[pallet::storage]
305
    pub type CollatorSignatureNonce<T: Config> = StorageMap<_, Twox128, ParaId, u64, ValueQuery>;
306

            
307
704
    #[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, DebugNoBound, TypeInfo)]
308
    #[scale_info(skip_type_params(T))]
309
    pub struct RelayXcmWeightConfigInner<T> {
310
        pub buy_execution_cost: u128,
311
        pub weight_at_most: Weight,
312
        pub _phantom: PhantomData<T>,
313
    }
314

            
315
    /// This must be set by root with the value of the relay chain xcm call weight and extrinsic
316
    /// weight limit. This is a storage item because relay chain weights can change, so we need to
317
    /// be able to adjust them without doing a runtime upgrade.
318
96
    #[pallet::storage]
319
    pub type RelayChain<T: Config> = StorageValue<_, T::RelayChain, ValueQuery>;
320

            
321
166
    #[pallet::call]
322
    impl<T: Config> Pallet<T> {
323
        /// Buy a core for this parathread id.
324
        /// Collators should call this to indicate that they intend to produce a block, but they
325
        /// cannot do it because this para id has no available cores.
326
        /// The purchase is automatic using XCM, and collators do not need to do anything.
327
        // Note that the collators that will be calling this function are parathread collators, not
328
        // tanssi collators. So we cannot force them to provide a complex proof, e.g. against relay
329
        // state.
330
        #[pallet::call_index(0)]
331
        #[pallet::weight(T::WeightInfo::buy_core())]
332
        pub fn buy_core(
333
            origin: OriginFor<T>,
334
            para_id: ParaId,
335
            // Below parameter are already validated during `validate_unsigned` call
336
            _collator_account_id: T::AccountId,
337
            _proof: BuyCoreCollatorProof<T::CollatorPublicKey>,
338
16
        ) -> DispatchResult {
339
16
            ensure_none(origin)?;
340

            
341
16
            let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
342
16
            CollatorSignatureNonce::<T>::set(para_id, current_nonce + 1);
343
16

            
344
16
            Self::on_collator_instantaneous_core_requested(para_id)
345
        }
346

            
347
        /// Buy core for para id as root. Does not require any proof, useful in tests.
348
        #[pallet::call_index(1)]
349
        #[pallet::weight(T::WeightInfo::force_buy_core())]
350
32
        pub fn force_buy_core(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
351
32
            ensure_root(origin)?;
352

            
353
31
            Self::on_collator_instantaneous_core_requested(para_id)
354
        }
355

            
356
        #[pallet::call_index(2)]
357
        #[pallet::weight(T::WeightInfo::set_relay_xcm_weight_config())]
358
        pub fn set_relay_xcm_weight_config(
359
            origin: OriginFor<T>,
360
            xcm_weights: Option<RelayXcmWeightConfigInner<T>>,
361
32
        ) -> DispatchResult {
362
32
            ensure_root(origin)?;
363

            
364
32
            if let Some(xcm_weights) = xcm_weights {
365
31
                RelayXcmWeightConfig::<T>::put(xcm_weights);
366
31
            } else {
367
1
                RelayXcmWeightConfig::<T>::kill();
368
1
            }
369

            
370
32
            Ok(())
371
        }
372

            
373
        #[pallet::call_index(3)]
374
        #[pallet::weight(T::WeightInfo::set_relay_chain())]
375
        pub fn set_relay_chain(
376
            origin: OriginFor<T>,
377
            relay_chain: Option<T::RelayChain>,
378
12
        ) -> DispatchResult {
379
12
            ensure_root(origin)?;
380

            
381
12
            if let Some(relay_chain) = relay_chain {
382
12
                RelayChain::<T>::put(relay_chain);
383
12
            } else {
384
                RelayChain::<T>::kill();
385
            }
386

            
387
12
            Ok(())
388
        }
389

            
390
        #[pallet::call_index(4)]
391
        #[pallet::weight(T::WeightInfo::query_response())]
392
        pub fn query_response(
393
            origin: OriginFor<T>,
394
            query_id: QueryId,
395
            response: Response,
396
26
        ) -> DispatchResult {
397
26
            let _responder = ensure_response(<T as Config>::RuntimeOrigin::from(origin))?;
398

            
399
26
            let maybe_para_id = QueryIdToParaId::<T>::get(query_id);
400

            
401
26
            let para_id = if let Some(para_id) = maybe_para_id {
402
26
                para_id
403
            } else {
404
                // Most probably entry was expired or removed in some other way. Let's return early.
405
                return Ok(());
406
            };
407

            
408
26
            QueryIdToParaId::<T>::remove(query_id);
409
26
            InFlightOrders::<T>::remove(para_id);
410

            
411
26
            match response {
412
12
                Response::DispatchResult(MaybeErrorCode::Success) => {
413
12
                    // Success. Add para id to pending block
414
12
                    let now = <frame_system::Pallet<T>>::block_number();
415
12
                    let ttl = T::PendingBlocksTtl::get();
416
12
                    PendingBlocks::<T>::insert(para_id, now + ttl);
417
12
                }
418
14
                Response::DispatchResult(_) => {
419
14
                    // We do not add paraid to pending block on failure
420
14
                }
421
                _ => {
422
                    // Unexpected.
423
                    return Err(Error::<T>::UnexpectedXCMResponse.into());
424
                }
425
            }
426

            
427
26
            Self::deposit_event(Event::ReceivedBuyCoreXCMResult { para_id, response });
428
26

            
429
26
            Ok(())
430
        }
431

            
432
        #[pallet::call_index(5)]
433
        #[pallet::weight(T::WeightInfo::clean_up_expired_in_flight_orders(expired_pending_blocks_para_id.len() as u32))]
434
        pub fn clean_up_expired_pending_blocks(
435
            origin: OriginFor<T>,
436
            expired_pending_blocks_para_id: Vec<ParaId>,
437
2
        ) -> DispatchResult {
438
2
            let _ = ensure_signed(origin)?;
439
2
            let now = frame_system::Pallet::<T>::block_number();
440
2
            let mut cleaned_up_para_ids = vec![];
441

            
442
4
            for para_id in expired_pending_blocks_para_id {
443
2
                let maybe_pending_block_ttl = PendingBlocks::<T>::get(para_id);
444
2
                if let Some(pending_block_ttl) = maybe_pending_block_ttl {
445
2
                    if pending_block_ttl < now {
446
1
                        PendingBlocks::<T>::remove(para_id);
447
1
                        cleaned_up_para_ids.push(para_id);
448
1
                    } else {
449
1
                        // Ignore if not expired
450
1
                    }
451
                }
452
            }
453

            
454
2
            Self::deposit_event(Event::CleanedUpExpiredPendingBlocksEntries {
455
2
                para_ids: cleaned_up_para_ids,
456
2
            });
457
2

            
458
2
            Ok(())
459
        }
460

            
461
        #[pallet::call_index(6)]
462
        #[pallet::weight(T::WeightInfo::clean_up_expired_in_flight_orders(expired_in_flight_orders.len() as u32))]
463
        pub fn clean_up_expired_in_flight_orders(
464
            origin: OriginFor<T>,
465
            expired_in_flight_orders: Vec<ParaId>,
466
2
        ) -> DispatchResult {
467
2
            let _ = ensure_signed(origin)?;
468
2
            let now = frame_system::Pallet::<T>::block_number();
469
2
            let mut cleaned_up_para_ids = vec![];
470

            
471
4
            for para_id in expired_in_flight_orders {
472
2
                let maybe_in_flight_order = InFlightOrders::<T>::get(para_id);
473
2
                if let Some(in_flight_order) = maybe_in_flight_order {
474
2
                    if in_flight_order.ttl < now {
475
1
                        InFlightOrders::<T>::remove(para_id);
476
1
                        QueryIdToParaId::<T>::remove(in_flight_order.query_id);
477
1
                        cleaned_up_para_ids.push(para_id);
478
1
                    } else {
479
1
                        // Ignore if not expired
480
1
                    }
481
                }
482
            }
483

            
484
2
            Self::deposit_event(Event::CleanedUpExpiredInFlightOrderEntries {
485
2
                para_ids: cleaned_up_para_ids,
486
2
            });
487
2

            
488
2
            Ok(())
489
        }
490
    }
491

            
492
    impl<T: Config> Pallet<T> {
493
        /// Returns the interior multilocation for this container chain para id. This is a relative
494
        /// multilocation that can be used in the `descend_origin` XCM opcode.
495
61
        pub fn interior_multilocation(para_id: ParaId) -> InteriorLocation {
496
61
            let container_chain_account = T::GetParathreadAccountId::convert(para_id);
497
61
            let account_junction = Junction::AccountId32 {
498
61
                id: container_chain_account,
499
61
                network: None,
500
61
            };
501
61

            
502
61
            [account_junction].into()
503
61
        }
504

            
505
        /// Returns a multilocation that can be used in the `deposit_asset` XCM opcode.
506
        /// The `interior_multilocation` can be obtained using `Self::interior_multilocation`.
507
61
        pub fn relay_relative_multilocation(
508
61
            interior_multilocation: InteriorLocation,
509
61
        ) -> Result<Location, Error<T>> {
510
61
            let relay_chain = Location::parent();
511
61
            let context: InteriorLocation = [Parachain(T::SelfParaId::get().into())].into();
512
61
            let mut reanchored: Location = interior_multilocation.into();
513
61
            reanchored
514
61
                .reanchor(&relay_chain, &context)
515
61
                .map_err(|_| Error::<T>::ReanchorFailed)?;
516

            
517
61
            Ok(reanchored)
518
61
        }
519

            
520
47
        pub fn is_core_buying_allowed(
521
47
            para_id: ParaId,
522
47
        ) -> Result<(), BuyingError<BlockNumberFor<T>>> {
523
47
            // If an in flight order is pending (i.e we did not receive the notification yet) and our
524
47
            // record is not expired yet, we should not allow the collator to buy another core.
525
47
            let maybe_in_flight_order = InFlightOrders::<T>::get(para_id);
526
47
            if let Some(in_flight_order) = maybe_in_flight_order {
527
8
                if in_flight_order.ttl < <frame_system::Pallet<T>>::block_number() {
528
3
                    InFlightOrders::<T>::remove(para_id);
529
3
                } else {
530
5
                    return Err(BuyingError::OrderAlreadyExists {
531
5
                        ttl: in_flight_order.ttl,
532
5
                        current_block_number: <frame_system::Pallet<T>>::block_number(),
533
5
                    });
534
                }
535
39
            }
536

            
537
            // If a block production is pending and our record is not expired yet, we should not allow
538
            // the collator to buy another core yet.
539
42
            let maybe_pending_blocks_ttl = PendingBlocks::<T>::get(para_id);
540
42
            if let Some(pending_blocks_ttl) = maybe_pending_blocks_ttl {
541
3
                if pending_blocks_ttl < <frame_system::Pallet<T>>::block_number() {
542
1
                    PendingBlocks::<T>::remove(para_id);
543
1
                } else {
544
2
                    return Err(BuyingError::BlockProductionPending {
545
2
                        ttl: pending_blocks_ttl,
546
2
                        current_block_number: <frame_system::Pallet<T>>::block_number(),
547
2
                    });
548
                }
549
39
            }
550

            
551
            // Check that the para id is a parathread
552
40
            let parathread_params = T::GetParathreadParams::get_parathread_params(para_id)
553
40
                .ok_or(BuyingError::NotAParathread)?;
554

            
555
39
            let maybe_latest_author_info =
556
39
                T::LatestAuthorInfoFetcher::get_latest_author_info(para_id);
557
39
            if let Some(latest_author_info) = maybe_latest_author_info {
558
25
                let current_slot = T::SlotBeacon::slot();
559
25
                if !parathread_params.slot_frequency.should_parathread_buy_core(
560
25
                    Slot::from(current_slot as u64),
561
25
                    Slot::from(2u64),
562
25
                    latest_author_info.latest_slot_number,
563
25
                ) {
564
                    // TODO: Take max slots to produce a block from config
565
2
                    return Err(BuyingError::NotAllowedToProduceBlockRightNow {
566
2
                        slot_frequency: parathread_params.slot_frequency,
567
2
                        max_slot_earlier_core_buying_permitted: Slot::from(2u64),
568
2
                        last_block_production_slot: latest_author_info.latest_slot_number,
569
2
                    });
570
23
                }
571
14
            }
572

            
573
37
            Ok(())
574
47
        }
575

            
576
        /// Send an XCM message to the relay chain to try to buy a core for this para_id.
577
47
        fn on_collator_instantaneous_core_requested(para_id: ParaId) -> DispatchResult {
578
47
            Self::is_core_buying_allowed(para_id).map_err(Into::<Error<T>>::into)?;
579

            
580
36
            let xcm_weights_storage =
581
37
                RelayXcmWeightConfig::<T>::get().ok_or(Error::<T>::XcmWeightStorageNotSet)?;
582

            
583
36
            let withdraw_amount = xcm_weights_storage.buy_execution_cost;
584
36

            
585
36
            // Use the account derived from the multilocation composed with DescendOrigin
586
36
            // Buy on-demand cores
587
36
            // Any failure should return everything to the derivative account
588
36

            
589
36
            // Don't use utility::as_derivative because that will make the tanssi sovereign account
590
36
            // pay for fees, instead use `DescendOrigin` to make the parathread tank account
591
36
            // pay for fees.
592
36
            // TODO: when coretime is implemented, use coretime instantaneous credits instead of
593
36
            // buying on-demand cores at the price defined by the relay
594
36
            let origin = OriginKind::SovereignAccount;
595
36
            // TODO: max_amount is the max price of a core that this parathread is willing to pay
596
36
            // It should be defined in a storage item somewhere, controllable by the container chain
597
36
            // manager.
598
36
            let max_amount =
599
36
                T::GetParathreadMaxCorePrice::get_max_core_price(para_id).unwrap_or(u128::MAX);
600
36
            let call =
601
36
                T::GetPurchaseCoreCall::get_encoded(RelayChain::<T>::get(), max_amount, para_id);
602
36
            let weight_at_most = xcm_weights_storage.weight_at_most;
603
36

            
604
36
            // Assumption: derived account already has DOT
605
36
            // The balance should be enough to cover the `Withdraw` needed to `BuyExecution`, plus
606
36
            // the price of the core, which can change based on demand.
607
36
            let relay_asset_total: Asset = (Here, withdraw_amount).into();
608
36
            let refund_asset_filter: AssetFilter = AssetFilter::Wild(WildAsset::AllCounted(1));
609
36

            
610
36
            let interior_multilocation = Self::interior_multilocation(para_id);
611
            // The parathread tank account is derived from the tanssi sovereign account and the
612
            // parathread para id.
613
36
            let derived_account =
614
36
                Self::relay_relative_multilocation(interior_multilocation.clone())?;
615

            
616
            // Need to use `builder_unsafe` because safe `builder` does not allow `descend_origin` as first instruction.
617
            // We use `descend_origin` instead of wrapping the transact call in `utility.as_derivative`
618
            // because with `descend_origin` the parathread tank account will pay for fees, while
619
            // `utility.as_derivative` will make the tanssi sovereign account pay for fees.
620

            
621
36
            let notify_call = <T as Config>::RuntimeCall::from(Call::<T>::query_response {
622
36
                query_id: 0,
623
36
                response: Default::default(),
624
36
            });
625
36
            let notify_call_weight = notify_call.get_dispatch_info().weight;
626
36

            
627
36
            let notify_query_ttl =
628
36
                <frame_system::Pallet<T>>::block_number() + T::CoreBuyingXCMQueryTtl::get();
629
36

            
630
36
            // Send XCM to relay chain
631
36
            let relay_chain = Location::parent();
632
36
            let query_id = T::XCMNotifier::new_notify_query(
633
36
                relay_chain.clone(),
634
36
                notify_call,
635
36
                notify_query_ttl,
636
36
                interior_multilocation.clone(),
637
36
            );
638

            
639
36
            let message: Xcm<()> = Xcm::builder_unsafe()
640
36
                .descend_origin(interior_multilocation.clone())
641
36
                .withdraw_asset(Assets::from(vec![relay_asset_total.clone()]))
642
36
                .buy_execution(relay_asset_total, Unlimited)
643
36
                // Both in case of error and in case of success, we want to refund the unused weight
644
36
                .set_appendix(
645
36
                    Xcm::builder_unsafe()
646
36
                        .report_transact_status(QueryResponseInfo {
647
36
                            destination: T::UniversalLocation::get()
648
36
                                .invert_target(&relay_chain)
649
36
                                .map_err(|_| Error::<T>::LocationInversionFailed)?, // This location from the point of view of destination
650
36
                            query_id,
651
36
                            max_weight: notify_call_weight,
652
36
                        })
653
36
                        .refund_surplus()
654
36
                        .deposit_asset(refund_asset_filter, derived_account)
655
36
                        .build(),
656
36
                )
657
36
                .transact(origin, weight_at_most, call)
658
36
                .build();
659

            
660
            // We intentionally do not charge any fees
661
36
            let (ticket, _price) =
662
36
                T::XcmSender::validate(&mut Some(relay_chain), &mut Some(message))
663
36
                    .map_err(|_| Error::<T>::ErrorValidatingXCM)?;
664
36
            T::XcmSender::deliver(ticket).map_err(|_| Error::<T>::ErrorDeliveringXCM)?;
665
36
            Self::deposit_event(Event::BuyCoreXcmSent {
666
36
                para_id,
667
36
                transaction_status_query_id: query_id,
668
36
            });
669
36

            
670
36
            let in_flight_order_ttl = notify_query_ttl + T::AdditionalTtlForInflightOrders::get();
671
36
            InFlightOrders::<T>::insert(
672
36
                para_id,
673
36
                InFlightCoreBuyingOrder {
674
36
                    para_id,
675
36
                    query_id,
676
36
                    ttl: in_flight_order_ttl,
677
36
                },
678
36
            );
679
36

            
680
36
            QueryIdToParaId::<T>::insert(query_id, para_id);
681
36

            
682
36
            Ok(())
683
47
        }
684

            
685
27
        pub fn para_deregistered(para_id: ParaId) {
686
            // If para is deregistered we need to clean up in flight order, query id mapping
687
27
            if let Some(in_flight_order) = InFlightOrders::<T>::take(para_id) {
688
1
                InFlightOrders::<T>::remove(para_id);
689
1
                QueryIdToParaId::<T>::remove(in_flight_order.query_id);
690
26
            }
691

            
692
            // We need to clean the pending block entry if any
693
27
            PendingBlocks::<T>::remove(para_id);
694
27
        }
695
    }
696

            
697
    #[pallet::validate_unsigned]
698
    impl<T: Config> ValidateUnsigned for Pallet<T> {
699
        type Call = Call<T>;
700

            
701
32
        fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
702
            if let Call::buy_core {
703
32
                para_id,
704
32
                collator_account_id,
705
32
                proof,
706
32
            } = call
707
            {
708
32
                let block_number = <frame_system::Pallet<T>>::block_number();
709
32

            
710
32
                let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
711
32
                if proof.nonce != current_nonce {
712
6
                    return InvalidTransaction::Call.into();
713
26
                }
714
26

            
715
26
                let is_valid_collator = T::CheckCollatorValidity::is_valid_collator(
716
26
                    *para_id,
717
26
                    collator_account_id.clone(),
718
26
                    proof.public_key.clone(),
719
26
                );
720
26
                if !is_valid_collator {
721
3
                    return InvalidTransaction::Call.into();
722
23
                }
723
23

            
724
23
                if !proof.verify_signature(*para_id) {
725
4
                    return InvalidTransaction::Call.into();
726
19
                }
727
19

            
728
19
                ValidTransaction::with_tag_prefix("XcmCoreBuyer")
729
19
                    .priority(T::UnsignedPriority::get())
730
19
                    // TODO: tags
731
19
                    .and_provides((block_number, para_id))
732
19
                    //.and_provides((current_session, authority_id))
733
19
                    //.longevity(
734
19
                    //    TryInto::<u64>::try_into(
735
19
                    //       T::NextSessionRotation::average_session_length() / 2u32.into(),
736
19
                    //    )
737
19
                    //        .unwrap_or(64_u64),
738
19
                    //)
739
19
                    .longevity(64)
740
19
                    .propagate(true)
741
19
                    .build()
742
            } else {
743
                InvalidTransaction::Call.into()
744
            }
745
32
        }
746
    }
747
}
748

            
749
pub trait GetPurchaseCoreCall<RelayChain> {
750
    /// Get the encoded call to buy a core for this `para_id`, with this `max_amount`.
751
    /// Returns the encoded call and its estimated weight.
752
    fn get_encoded(relay_chain: RelayChain, max_amount: u128, para_id: ParaId) -> Vec<u8>;
753
}
754

            
755
pub trait CheckCollatorValidity<AccountId, PublicKey> {
756
    fn is_valid_collator(para_id: ParaId, account_id: AccountId, public_key: PublicKey) -> bool;
757

            
758
    #[cfg(feature = "runtime-benchmarks")]
759
    fn set_valid_collator(para_id: ParaId, account_id: AccountId, public_key: PublicKey);
760
}
761

            
762
pub trait GetParathreadMaxCorePrice {
763
    fn get_max_core_price(para_id: ParaId) -> Option<u128>;
764
}
765

            
766
impl GetParathreadMaxCorePrice for () {
767
20
    fn get_max_core_price(_para_id: ParaId) -> Option<u128> {
768
20
        None
769
20
    }
770
}
771

            
772
pub trait GetParathreadParams {
773
    fn get_parathread_params(para_id: ParaId) -> Option<ParathreadParams>;
774

            
775
    #[cfg(feature = "runtime-benchmarks")]
776
    fn set_parathread_params(para_id: ParaId, parathread_params: Option<ParathreadParams>);
777
}
778

            
779
/// Use `into_account_truncating` to convert a `ParaId` into a `[u8; 32]`.
780
pub struct ParaIdIntoAccountTruncating;
781

            
782
impl Convert<ParaId, [u8; 32]> for ParaIdIntoAccountTruncating {
783
133
    fn convert(para_id: ParaId) -> [u8; 32] {
784
133
        // Derive a 32 byte account id for a parathread. Note that this is not the address of
785
133
        // the relay chain parathread tank, but that address is derived from this.
786
133
        let account: dp_core::AccountId = para_id.into_account_truncating();
787
133

            
788
133
        account.into()
789
133
    }
790
}