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 {
38
    dp_core::ParaId,
39
    frame_support::{
40
        dispatch::GetDispatchInfo,
41
        pallet_prelude::*,
42
        traits::fungible::{Balanced, Inspect},
43
    },
44
    frame_system::pallet_prelude::*,
45
    parity_scale_codec::EncodeLike,
46
    sp_consensus_slots::Slot,
47
    sp_runtime::traits::{AccountIdConversion, Convert, Get},
48
    sp_std::{vec, vec::Vec},
49
    staging_xcm::{
50
        latest::{Asset, Assets, InteriorLocation, Response, Xcm},
51
        prelude::*,
52
    },
53
    tp_traits::{
54
        AuthorNotingHook, BlockNumber, LatestAuthorInfoFetcher, ParathreadParams, SlotFrequency,
55
    },
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
704
#[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo, Serialize, Deserialize)]
81
pub struct InFlightCoreBuyingOrder<BN> {
82
    para_id: ParaId,
83
    query_id: QueryId,
84
    ttl: BN,
85
}
86

            
87
#[derive(
88
    Debug, Clone, PartialEq, Eq, Encode, Decode, scale_info::TypeInfo, Serialize, Deserialize,
89
)]
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
8718
#[frame_support::pallet]
120
pub mod pallet {
121
    use sp_runtime::app_crypto::AppCrypto;
122
    use {
123
        super::*, nimbus_primitives::SlotBeacon, pallet_xcm::ensure_response,
124
        sp_runtime::RuntimeAppPublic,
125
    };
126

            
127
13616
    #[pallet::pallet]
128
    #[pallet::without_storage_info]
129
    pub struct Pallet<T>(PhantomData<T>);
130

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

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

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

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

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

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

            
186
        /// Slot drift allowed for core buying
187
        #[pallet::constant]
188
        type BuyCoreSlotDrift: Get<Slot>;
189

            
190
        #[pallet::constant]
191
        type UniversalLocation: Get<InteriorLocation>;
192

            
193
        type RuntimeOrigin: Into<Result<pallet_xcm::Origin, <Self as Config>::RuntimeOrigin>>
194
            + From<<Self as frame_system::Config>::RuntimeOrigin>;
195

            
196
        /// The overarching call type
197
        type RuntimeCall: From<Call<Self>> + Encode + GetDispatchInfo;
198

            
199
        /// Outcome notifier implements functionality to enable reporting back the outcome
200
        type XCMNotifier: XCMNotifier<Self>;
201

            
202
        type LatestAuthorInfoFetcher: LatestAuthorInfoFetcher<Self::AccountId>;
203

            
204
        type SlotBeacon: SlotBeacon;
205

            
206
        /// A PublicKey can be converted into an `AccountId`. This is required in order to verify
207
        /// the collator signature
208
        type CollatorPublicKey: Member
209
            + Parameter
210
            + RuntimeAppPublic
211
            + AppCrypto
212
            + MaybeSerializeDeserialize
213
            + MaxEncodedLen;
214

            
215
        type WeightInfo: WeightInfo;
216
    }
217

            
218
    #[pallet::event]
219
50
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
220
    pub enum Event<T: Config> {
221
32
        /// An XCM message to buy a core for this parathread has been sent to the relay chain.
222
        BuyCoreXcmSent {
223
            para_id: ParaId,
224
            transaction_status_query_id: QueryId,
225
        },
226
11
        /// We received response for xcm
227
        ReceivedBuyCoreXCMResult { para_id: ParaId, response: Response },
228

            
229
2
        /// We cleaned up expired pending blocks entries.
230
        CleanedUpExpiredPendingBlocksEntries { para_ids: Vec<ParaId> },
231

            
232
2
        /// We cleaned up expired in flight orders entries.
233
        CleanedUpExpiredInFlightOrderEntries { para_ids: Vec<ParaId> },
234
    }
235

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

            
273
    impl<T: Config> From<BuyingError<BlockNumberFor<T>>> for Error<T> {
274
10
        fn from(value: BuyingError<BlockNumberFor<T>>) -> Self {
275
10
            match value {
276
5
                BuyingError::OrderAlreadyExists { .. } => Error::<T>::OrderAlreadyExists,
277
2
                BuyingError::BlockProductionPending { .. } => Error::<T>::BlockProductionPending,
278
1
                BuyingError::NotAParathread => Error::<T>::NotAParathread,
279
                BuyingError::NotAllowedToProduceBlockRightNow { .. } => {
280
2
                    Error::<T>::NotAllowedToProduceBlockRightNow
281
                }
282
            }
283
10
        }
284
    }
285

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

            
293
    /// Number of pending blocks
294
6652
    #[pallet::storage]
295
    pub type PendingBlocks<T: Config> =
296
        StorageMap<_, Twox128, ParaId, BlockNumberFor<T>, OptionQuery>;
297

            
298
    /// Mapping of QueryId to ParaId
299
79
    #[pallet::storage]
300
    pub type QueryIdToParaId<T: Config> = StorageMap<_, Twox128, QueryId, ParaId, OptionQuery>;
301

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

            
309
    /// Collator signature nonce for reply protection
310
50
    #[pallet::storage]
311
    pub type CollatorSignatureNonce<T: Config> = StorageMap<_, Twox128, ParaId, u64, ValueQuery>;
312

            
313
704
    #[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, DebugNoBound, TypeInfo)]
314
    #[scale_info(skip_type_params(T))]
315
    pub struct RelayXcmWeightConfigInner<T> {
316
        pub buy_execution_cost: u128,
317
        pub weight_at_most: Weight,
318
        pub _phantom: PhantomData<T>,
319
    }
320

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

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

            
346
10
            let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
347
10
            CollatorSignatureNonce::<T>::set(para_id, current_nonce + 1);
348
10

            
349
10
            Self::on_collator_instantaneous_core_requested(para_id, Some(proof.public_key))
350
        }
351

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

            
358
31
            Self::on_collator_instantaneous_core_requested(para_id, None)
359
        }
360

            
361
        #[pallet::call_index(2)]
362
        #[pallet::weight(T::WeightInfo::set_relay_xcm_weight_config())]
363
        pub fn set_relay_xcm_weight_config(
364
            origin: OriginFor<T>,
365
            xcm_weights: Option<RelayXcmWeightConfigInner<T>>,
366
26
        ) -> DispatchResult {
367
26
            ensure_root(origin)?;
368

            
369
26
            if let Some(xcm_weights) = xcm_weights {
370
25
                RelayXcmWeightConfig::<T>::put(xcm_weights);
371
25
            } else {
372
1
                RelayXcmWeightConfig::<T>::kill();
373
1
            }
374

            
375
26
            Ok(())
376
        }
377

            
378
        #[pallet::call_index(3)]
379
        #[pallet::weight(T::WeightInfo::set_relay_chain())]
380
        pub fn set_relay_chain(
381
            origin: OriginFor<T>,
382
            relay_chain: Option<T::RelayChain>,
383
6
        ) -> DispatchResult {
384
6
            ensure_root(origin)?;
385

            
386
6
            if let Some(relay_chain) = relay_chain {
387
6
                RelayChain::<T>::put(relay_chain);
388
6
            } else {
389
                RelayChain::<T>::kill();
390
            }
391

            
392
6
            Ok(())
393
        }
394

            
395
        #[pallet::call_index(4)]
396
        #[pallet::weight(T::WeightInfo::query_response())]
397
        pub fn query_response(
398
            origin: OriginFor<T>,
399
            query_id: QueryId,
400
            response: Response,
401
16
        ) -> DispatchResult {
402
16
            let _responder = ensure_response(<T as Config>::RuntimeOrigin::from(origin))?;
403

            
404
16
            let maybe_para_id = QueryIdToParaId::<T>::get(query_id);
405

            
406
16
            let para_id = if let Some(para_id) = maybe_para_id {
407
16
                para_id
408
            } else {
409
                // Most probably entry was expired or removed in some other way. Let's return early.
410
                return Ok(());
411
            };
412

            
413
16
            QueryIdToParaId::<T>::remove(query_id);
414
16
            InFlightOrders::<T>::remove(para_id);
415

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

            
432
16
            Self::deposit_event(Event::ReceivedBuyCoreXCMResult { para_id, response });
433
16

            
434
16
            Ok(())
435
        }
436

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

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

            
459
2
            Self::deposit_event(Event::CleanedUpExpiredPendingBlocksEntries {
460
2
                para_ids: cleaned_up_para_ids,
461
2
            });
462
2

            
463
2
            Ok(())
464
        }
465

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

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

            
489
2
            Self::deposit_event(Event::CleanedUpExpiredInFlightOrderEntries {
490
2
                para_ids: cleaned_up_para_ids,
491
2
            });
492
2

            
493
2
            Ok(())
494
        }
495
    }
496

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

            
507
43
            [account_junction].into()
508
43
        }
509

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

            
522
43
            Ok(reanchored)
523
43
        }
524

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

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

            
557
            // Check that the para id is a parathread
558
34
            let parathread_params = T::GetParathreadParams::get_parathread_params(para_id)
559
34
                .ok_or(BuyingError::NotAParathread)?;
560

            
561
33
            let maybe_latest_author_info =
562
33
                T::LatestAuthorInfoFetcher::get_latest_author_info(para_id);
563
33
            if let Some(latest_author_info) = maybe_latest_author_info {
564
25
                let current_slot = T::SlotBeacon::slot();
565
25
                if !parathread_params.slot_frequency.should_parathread_buy_core(
566
25
                    Slot::from(current_slot as u64),
567
25
                    T::BuyCoreSlotDrift::get(),
568
25
                    latest_author_info.latest_slot_number,
569
25
                ) {
570
                    // TODO: Take max slots to produce a block from config
571
2
                    return Err(BuyingError::NotAllowedToProduceBlockRightNow {
572
2
                        slot_frequency: parathread_params.slot_frequency,
573
2
                        max_slot_earlier_core_buying_permitted: Slot::from(2u64),
574
2
                        last_block_production_slot: latest_author_info.latest_slot_number,
575
2
                    });
576
23
                }
577
8
            }
578

            
579
31
            Ok(())
580
41
        }
581

            
582
        /// Send an XCM message to the relay chain to try to buy a core for this para_id.
583
41
        fn on_collator_instantaneous_core_requested(
584
41
            para_id: ParaId,
585
41
            maybe_collator_public_key: Option<<T as Config>::CollatorPublicKey>,
586
41
        ) -> DispatchResult {
587
41
            Self::is_core_buying_allowed(para_id, maybe_collator_public_key)
588
41
                .map_err(Into::<Error<T>>::into)?;
589

            
590
30
            let xcm_weights_storage =
591
31
                RelayXcmWeightConfig::<T>::get().ok_or(Error::<T>::XcmWeightStorageNotSet)?;
592

            
593
30
            let withdraw_amount = xcm_weights_storage.buy_execution_cost;
594
30

            
595
30
            // Use the account derived from the multilocation composed with DescendOrigin
596
30
            // Buy on-demand cores
597
30
            // Any failure should return everything to the derivative account
598
30

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

            
614
30
            // Assumption: derived account already has DOT
615
30
            // The balance should be enough to cover the `Withdraw` needed to `BuyExecution`, plus
616
30
            // the price of the core, which can change based on demand.
617
30
            let relay_asset_total: Asset = (Here, withdraw_amount).into();
618
30
            let refund_asset_filter: AssetFilter = AssetFilter::Wild(WildAsset::AllCounted(1));
619
30

            
620
30
            let interior_multilocation = Self::interior_multilocation(para_id);
621
            // The parathread tank account is derived from the tanssi sovereign account and the
622
            // parathread para id.
623
30
            let derived_account =
624
30
                Self::relay_relative_multilocation(interior_multilocation.clone())?;
625

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

            
631
30
            let notify_call = <T as Config>::RuntimeCall::from(Call::<T>::query_response {
632
30
                query_id: 0,
633
30
                response: Default::default(),
634
30
            });
635
30
            let notify_call_weight = notify_call.get_dispatch_info().weight;
636
30

            
637
30
            let notify_query_ttl =
638
30
                <frame_system::Pallet<T>>::block_number() + T::CoreBuyingXCMQueryTtl::get();
639
30

            
640
30
            // Send XCM to relay chain
641
30
            let relay_chain = Location::parent();
642
30
            let query_id = T::XCMNotifier::new_notify_query(
643
30
                relay_chain.clone(),
644
30
                notify_call,
645
30
                notify_query_ttl,
646
30
                interior_multilocation.clone(),
647
30
            );
648

            
649
30
            let message: Xcm<()> = Xcm::builder_unsafe()
650
30
                .descend_origin(interior_multilocation.clone())
651
30
                .withdraw_asset(Assets::from(vec![relay_asset_total.clone()]))
652
30
                .buy_execution(relay_asset_total, Unlimited)
653
30
                // Both in case of error and in case of success, we want to refund the unused weight
654
30
                .set_appendix(
655
30
                    Xcm::builder_unsafe()
656
30
                        .report_transact_status(QueryResponseInfo {
657
30
                            destination: T::UniversalLocation::get()
658
30
                                .invert_target(&relay_chain)
659
30
                                .map_err(|_| Error::<T>::LocationInversionFailed)?, // This location from the point of view of destination
660
30
                            query_id,
661
30
                            max_weight: notify_call_weight,
662
30
                        })
663
30
                        .refund_surplus()
664
30
                        .deposit_asset(refund_asset_filter, derived_account)
665
30
                        .build(),
666
30
                )
667
30
                .transact(origin, weight_at_most, call)
668
30
                .build();
669

            
670
            // We intentionally do not charge any fees
671
30
            let (ticket, _price) =
672
30
                T::XcmSender::validate(&mut Some(relay_chain), &mut Some(message))
673
30
                    .map_err(|_| Error::<T>::ErrorValidatingXCM)?;
674
30
            T::XcmSender::deliver(ticket).map_err(|_| Error::<T>::ErrorDeliveringXCM)?;
675
30
            Self::deposit_event(Event::BuyCoreXcmSent {
676
30
                para_id,
677
30
                transaction_status_query_id: query_id,
678
30
            });
679
30

            
680
30
            let in_flight_order_ttl = notify_query_ttl + T::AdditionalTtlForInflightOrders::get();
681
30
            InFlightOrders::<T>::insert(
682
30
                para_id,
683
30
                InFlightCoreBuyingOrder {
684
30
                    para_id,
685
30
                    query_id,
686
30
                    ttl: in_flight_order_ttl,
687
30
                },
688
30
            );
689
30

            
690
30
            QueryIdToParaId::<T>::insert(query_id, para_id);
691
30

            
692
30
            Ok(())
693
41
        }
694

            
695
27
        pub fn para_deregistered(para_id: ParaId) {
696
            // If para is deregistered we need to clean up in flight order, query id mapping
697
27
            if let Some(in_flight_order) = InFlightOrders::<T>::take(para_id) {
698
1
                InFlightOrders::<T>::remove(para_id);
699
1
                QueryIdToParaId::<T>::remove(in_flight_order.query_id);
700
26
            }
701

            
702
            // We need to clean the pending block entry if any
703
27
            PendingBlocks::<T>::remove(para_id);
704
27
        }
705
    }
706

            
707
    #[pallet::validate_unsigned]
708
    impl<T: Config> ValidateUnsigned for Pallet<T> {
709
        type Call = Call<T>;
710

            
711
26
        fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
712
26
            if let Call::buy_core { para_id, proof } = call {
713
26
                let block_number = <frame_system::Pallet<T>>::block_number();
714
26

            
715
26
                let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
716
26
                if proof.nonce != current_nonce {
717
6
                    return InvalidTransaction::Call.into();
718
20
                }
719
20

            
720
20
                let is_valid_collator =
721
20
                    T::CheckCollatorValidity::is_valid_collator(*para_id, proof.public_key.clone());
722
20
                if !is_valid_collator {
723
3
                    return InvalidTransaction::Call.into();
724
17
                }
725
17

            
726
17
                if !proof.verify_signature(*para_id) {
727
4
                    return InvalidTransaction::Call.into();
728
13
                }
729
13

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

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

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

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

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

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

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

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

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

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

            
790
123
        account.into()
791
123
    }
792
}