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
use sp_runtime::{SaturatedConversion, Saturating};
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
pub mod weights;
36
pub use weights::WeightInfo;
37

            
38
#[cfg(feature = "runtime-benchmarks")]
39
use tp_traits::BlockNumber;
40
use {
41
    dp_core::ParaId,
42
    frame_support::{
43
        dispatch::GetDispatchInfo,
44
        pallet_prelude::*,
45
        traits::fungible::{Balanced, Inspect},
46
    },
47
    frame_system::pallet_prelude::*,
48
    parity_scale_codec::EncodeLike,
49
    sp_consensus_slots::Slot,
50
    sp_runtime::traits::{AccountIdConversion, Convert, Get},
51
    sp_std::{vec, vec::Vec},
52
    tp_traits::{
53
        AuthorNotingHook, AuthorNotingInfo, LatestAuthorInfoFetcher, ParathreadParams,
54
        SlotFrequency,
55
    },
56
    tp_xcm_core_buyer::BuyCoreCollatorProof,
57
    xcm::{
58
        latest::{Asset, Assets, InteriorLocation, Response, Xcm},
59
        prelude::*,
60
    },
61
};
62

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

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

            
84
#[derive(
85
    RuntimeDebug,
86
    PartialEq,
87
    Eq,
88
    Encode,
89
    Decode,
90
    Clone,
91
1854
    TypeInfo,
92
    Serialize,
93
    Deserialize,
94
    MaxEncodedLen,
95
)]
96
pub struct InFlightCoreBuyingOrder<BN> {
97
    para_id: ParaId,
98
    query_id: QueryId,
99
    ttl: BN,
100
}
101

            
102
#[derive(
103
4224
    Debug, Clone, PartialEq, Eq, Encode, Decode, scale_info::TypeInfo, Serialize, Deserialize,
104
)]
105
pub enum BuyingError<BlockNumber> {
106
    OrderAlreadyExists {
107
        ttl: BlockNumber,
108
        current_block_number: BlockNumber,
109
    },
110
    BlockProductionPending {
111
        ttl: BlockNumber,
112
        current_block_number: BlockNumber,
113
    },
114
    NotAParathread,
115
    NotAllowedToProduceBlockRightNow {
116
        slot_frequency: SlotFrequency,
117
        max_slot_earlier_core_buying_permitted: Slot,
118
        last_block_production_slot: Slot,
119
    },
120
}
121

            
122
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
123
22410
    fn on_container_authors_noted(info: &[AuthorNotingInfo<T::AccountId>]) -> Weight {
124
22410
        let writes = info.len().saturated_into();
125

            
126
44947
        for info in info {
127
22537
            let para_id = info.para_id;
128
22537
            PendingBlocks::<T>::remove(para_id);
129
22537
        }
130

            
131
22410
        T::DbWeight::get().writes(writes)
132
22410
    }
133

            
134
    #[cfg(feature = "runtime-benchmarks")]
135
    fn prepare_worst_case_for_bench(
136
        _author: &T::AccountId,
137
        _block_number: BlockNumber,
138
        para_id: ParaId,
139
    ) {
140
        // We insert the some data in the storage being removed.
141
        // Not sure if this is necessary.
142
        PendingBlocks::<T>::insert(para_id, BlockNumberFor::<T>::from(42u32));
143
    }
144
}
145

            
146
22866
#[frame_support::pallet]
147
pub mod pallet {
148
    use {
149
        super::*,
150
        nimbus_primitives::SlotBeacon,
151
        pallet_xcm::ensure_response,
152
        sp_runtime::{app_crypto::AppCrypto, RuntimeAppPublic},
153
    };
154

            
155
68426
    #[pallet::pallet]
156
    pub struct Pallet<T>(PhantomData<T>);
157

            
158
    #[pallet::config]
159
    pub trait Config: frame_system::Config {
160
        /// Overarching event type.
161
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
162
        type Currency: Inspect<Self::AccountId> + Balanced<Self::AccountId>;
163

            
164
        type XcmSender: SendXcm;
165
        /// Get encoded call to buy a core in the relay chain. This will be passed to the XCM
166
        /// `Transact` instruction.
167
        type GetPurchaseCoreCall: GetPurchaseCoreCall<Self::RelayChain>;
168
        /// How to convert a `ParaId` into an `AccountId32`. Used to derive the parathread tank
169
        /// account in `interior_multilocation`.
170
        type GetParathreadAccountId: Convert<ParaId, [u8; 32]>;
171
        /// The max price that the parathread is willing to pay for a core, in relay chain currency.
172
        /// If `None`, defaults to `u128::MAX`, the parathread will pay the market price with no
173
        /// upper bound.
174
        type GetParathreadMaxCorePrice: GetParathreadMaxCorePrice;
175
        /// Orchestartor chain `ParaId`. Used in `absolute_multilocation` to convert the
176
        /// `interior_multilocation` into what the relay chain needs to allow to `DepositAsset`.
177
        type SelfParaId: Get<ParaId>;
178
        type RelayChain: Default
179
            + Encode
180
            + Decode
181
            + TypeInfo
182
            + EncodeLike
183
            + Clone
184
            + PartialEq
185
            + sp_std::fmt::Debug
186
            + MaxEncodedLen;
187

            
188
        /// Get the parathread params. Used to verify that the para id is a parathread.
189
        // TODO: and in the future to restrict the ability to buy a core depending on slot frequency
190
        type GetParathreadParams: GetParathreadParams;
191
        /// Validate if particular account id and public key pair belongs to a collator and the collator
192
        /// is selected to collate for particular para id.
193
        type CheckCollatorValidity: CheckCollatorValidity<Self::AccountId, Self::CollatorPublicKey>;
194
        /// A configuration for base priority of unsigned transactions.
195
        ///
196
        /// This is exposed so that it can be tuned for particular runtime, when
197
        /// multiple pallets send unsigned transactions.
198
        #[pallet::constant]
199
        type UnsignedPriority: Get<TransactionPriority>;
200

            
201
        /// TTL for pending blocks entry, which prevents anyone to submit another core buying xcm.
202
        #[pallet::constant]
203
        type PendingBlocksTtl: Get<BlockNumberFor<Self>>;
204

            
205
        /// TTL to be used in xcm's notify query
206
        #[pallet::constant]
207
        type CoreBuyingXCMQueryTtl: Get<BlockNumberFor<Self>>;
208

            
209
        /// Additional ttl for in flight orders (total would be CoreBuyingXCMQueryTtl + AdditionalTtlForInflightOrders)
210
        /// after which the in flight orders can be cleaned up by anyone.
211
        #[pallet::constant]
212
        type AdditionalTtlForInflightOrders: Get<BlockNumberFor<Self>>;
213

            
214
        /// Slot drift allowed for core buying
215
        #[pallet::constant]
216
        type BuyCoreSlotDrift: Get<Slot>;
217

            
218
        #[pallet::constant]
219
        type UniversalLocation: Get<InteriorLocation>;
220

            
221
        type RuntimeOrigin: Into<Result<pallet_xcm::Origin, <Self as Config>::RuntimeOrigin>>
222
            + From<<Self as frame_system::Config>::RuntimeOrigin>;
223

            
224
        /// The overarching call type
225
        type RuntimeCall: From<Call<Self>> + Encode + GetDispatchInfo;
226

            
227
        /// Outcome notifier implements functionality to enable reporting back the outcome
228
        type XCMNotifier: XCMNotifier<Self>;
229

            
230
        type LatestAuthorInfoFetcher: LatestAuthorInfoFetcher<Self::AccountId>;
231

            
232
        type SlotBeacon: SlotBeacon;
233

            
234
        /// A PublicKey can be converted into an `AccountId`. This is required in order to verify
235
        /// the collator signature
236
        type CollatorPublicKey: Member
237
            + Parameter
238
            + RuntimeAppPublic
239
            + AppCrypto
240
            + MaybeSerializeDeserialize
241
            + MaxEncodedLen;
242

            
243
        type WeightInfo: WeightInfo;
244
    }
245

            
246
618
    #[pallet::event]
247
124
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
248
    pub enum Event<T: Config> {
249
        /// An XCM message to buy a core for this parathread has been sent to the relay chain.
250
        BuyCoreXcmSent {
251
            para_id: ParaId,
252
            transaction_status_query_id: QueryId,
253
        },
254
        /// We received response for xcm
255
        ReceivedBuyCoreXCMResult { para_id: ParaId, response: Response },
256

            
257
        /// We cleaned up expired pending blocks entries.
258
        CleanedUpExpiredPendingBlocksEntries { para_ids: Vec<ParaId> },
259

            
260
        /// We cleaned up expired in flight orders entries.
261
        CleanedUpExpiredInFlightOrderEntries { para_ids: Vec<ParaId> },
262
    }
263

            
264
618
    #[pallet::error]
265
    pub enum Error<T> {
266
        InvalidProof,
267
        ErrorValidatingXCM,
268
        ErrorDeliveringXCM,
269
        /// An order for this para id already exists
270
        OrderAlreadyExists,
271
        /// The para id is not a parathread
272
        NotAParathread,
273
        /// There are too many in-flight orders, buying cores will not work until some of those
274
        /// orders finish.
275
        InFlightLimitReached,
276
        /// There are no collators assigned to this parathread, so no point in buying a core
277
        NoAssignedCollators,
278
        /// This collator is not assigned to this parathread
279
        CollatorNotAssigned,
280
        /// The `XcmWeights` storage has not been set. This must have been set by root with the
281
        /// value of the relay chain xcm call weight and extrinsic weight
282
        XcmWeightStorageNotSet,
283
        /// Converting a multilocation into a relay relative multilocation failed
284
        ReanchorFailed,
285
        /// Inverting location from destination point of view failed
286
        LocationInversionFailed,
287
        /// Modifying XCM to report the result of XCM failed
288
        ReportNotifyingSetupFailed,
289
        /// Unexpected XCM response
290
        UnexpectedXCMResponse,
291
        /// Block production is pending for para id with successfully placed order
292
        BlockProductionPending,
293
        /// Block production is not allowed for current slot
294
        NotAllowedToProduceBlockRightNow,
295
        /// Collator signature nonce is incorrect
296
        IncorrectCollatorSignatureNonce,
297
        /// Collator signature is invalid
298
        InvalidCollatorSignature,
299
    }
300

            
301
    impl<T: Config> From<BuyingError<BlockNumberFor<T>>> for Error<T> {
302
10
        fn from(value: BuyingError<BlockNumberFor<T>>) -> Self {
303
10
            match value {
304
5
                BuyingError::OrderAlreadyExists { .. } => Error::<T>::OrderAlreadyExists,
305
2
                BuyingError::BlockProductionPending { .. } => Error::<T>::BlockProductionPending,
306
1
                BuyingError::NotAParathread => Error::<T>::NotAParathread,
307
                BuyingError::NotAllowedToProduceBlockRightNow { .. } => {
308
2
                    Error::<T>::NotAllowedToProduceBlockRightNow
309
                }
310
            }
311
10
        }
312
    }
313

            
314
    /// Set of parathreads that have already sent an XCM message to buy a core recently.
315
    /// Used to avoid 2 collators buying a core at the same time, because it is only possible to buy
316
    /// 1 core in 1 relay block for the same parathread.
317
901
    #[pallet::storage]
318
    pub type InFlightOrders<T: Config> =
319
        StorageMap<_, Twox128, ParaId, InFlightCoreBuyingOrder<BlockNumberFor<T>>, OptionQuery>;
320

            
321
    /// Number of pending blocks
322
23335
    #[pallet::storage]
323
    pub type PendingBlocks<T: Config> =
324
        StorageMap<_, Twox128, ParaId, BlockNumberFor<T>, OptionQuery>;
325

            
326
    /// Mapping of QueryId to ParaId
327
837
    #[pallet::storage]
328
    pub type QueryIdToParaId<T: Config> = StorageMap<_, Twox128, QueryId, ParaId, OptionQuery>;
329

            
330
    /// This must be set by root with the value of the relay chain xcm call weight and extrinsic
331
    /// weight limit. This is a storage item because relay chain weights can change, so we need to
332
    /// be able to adjust them without doing a runtime upgrade.
333
780
    #[pallet::storage]
334
    pub type RelayXcmWeightConfig<T: Config> =
335
        StorageValue<_, RelayXcmWeightConfigInner<T>, OptionQuery>;
336

            
337
    /// Collator signature nonce for reply protection
338
724
    #[pallet::storage]
339
    pub type CollatorSignatureNonce<T: Config> = StorageMap<_, Twox128, ParaId, u64, ValueQuery>;
340

            
341
    #[derive(
342
        Encode,
343
        Decode,
344
        CloneNoBound,
345
        PartialEq,
346
        Eq,
347
        DebugNoBound,
348
1854
        TypeInfo,
349
        MaxEncodedLen,
350
        DecodeWithMemTracking,
351
    )]
352
    #[scale_info(skip_type_params(T))]
353
    pub struct RelayXcmWeightConfigInner<T> {
354
        pub buy_execution_cost: u128,
355
        pub weight_at_most: Weight,
356
        pub _phantom: PhantomData<T>,
357
    }
358

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

            
365
618
    #[pallet::call]
366
    impl<T: Config> Pallet<T> {
367
        /// Buy a core for this parathread id.
368
        /// Collators should call this to indicate that they intend to produce a block, but they
369
        /// cannot do it because this para id has no available cores.
370
        /// The purchase is automatic using XCM, and collators do not need to do anything.
371
        // Note that the collators that will be calling this function are parathread collators, not
372
        // tanssi collators. So we cannot force them to provide a complex proof, e.g. against relay
373
        // state.
374
        #[pallet::call_index(0)]
375
        #[pallet::weight(T::WeightInfo::buy_core())]
376
        pub fn buy_core(
377
            origin: OriginFor<T>,
378
            para_id: ParaId,
379
            // Below parameter are already validated during `validate_unsigned` cal
380
            proof: BuyCoreCollatorProof<T::CollatorPublicKey>,
381
20
        ) -> DispatchResult {
382
20
            ensure_none(origin)?;
383

            
384
20
            let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
385
20
            CollatorSignatureNonce::<T>::set(para_id, current_nonce.saturating_add(1));
386
20

            
387
20
            Self::on_collator_instantaneous_core_requested(para_id, Some(proof.public_key))
388
        }
389

            
390
        /// Buy core for para id as root. Does not require any proof, useful in tests.
391
        #[pallet::call_index(1)]
392
        #[pallet::weight(T::WeightInfo::force_buy_core())]
393
36
        pub fn force_buy_core(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
394
36
            ensure_root(origin)?;
395

            
396
35
            Self::on_collator_instantaneous_core_requested(para_id, None)
397
        }
398

            
399
        #[pallet::call_index(2)]
400
        #[pallet::weight(T::WeightInfo::set_relay_xcm_weight_config())]
401
        pub fn set_relay_xcm_weight_config(
402
            origin: OriginFor<T>,
403
            xcm_weights: Option<RelayXcmWeightConfigInner<T>>,
404
36
        ) -> DispatchResult {
405
36
            ensure_root(origin)?;
406

            
407
36
            if let Some(xcm_weights) = xcm_weights {
408
35
                RelayXcmWeightConfig::<T>::put(xcm_weights);
409
35
            } else {
410
1
                RelayXcmWeightConfig::<T>::kill();
411
1
            }
412

            
413
36
            Ok(())
414
        }
415

            
416
        #[pallet::call_index(3)]
417
        #[pallet::weight(T::WeightInfo::set_relay_chain())]
418
        pub fn set_relay_chain(
419
            origin: OriginFor<T>,
420
            relay_chain: Option<T::RelayChain>,
421
12
        ) -> DispatchResult {
422
12
            ensure_root(origin)?;
423

            
424
12
            if let Some(relay_chain) = relay_chain {
425
12
                RelayChain::<T>::put(relay_chain);
426
12
            } else {
427
                RelayChain::<T>::kill();
428
            }
429

            
430
12
            Ok(())
431
        }
432

            
433
        #[pallet::call_index(4)]
434
        #[pallet::weight(T::WeightInfo::query_response())]
435
        pub fn query_response(
436
            origin: OriginFor<T>,
437
            query_id: QueryId,
438
            response: Response,
439
76
        ) -> DispatchResult {
440
76
            let _responder = ensure_response(<T as Config>::RuntimeOrigin::from(origin))?;
441

            
442
76
            let maybe_para_id = QueryIdToParaId::<T>::get(query_id);
443

            
444
76
            let para_id = if let Some(para_id) = maybe_para_id {
445
76
                para_id
446
            } else {
447
                // Most probably entry was expired or removed in some other way. Let's return early.
448
                return Ok(());
449
            };
450

            
451
76
            QueryIdToParaId::<T>::remove(query_id);
452
76
            InFlightOrders::<T>::remove(para_id);
453

            
454
76
            match response {
455
32
                Response::DispatchResult(MaybeErrorCode::Success) => {
456
32
                    // Success. Add para id to pending block
457
32
                    let now = <frame_system::Pallet<T>>::block_number();
458
32
                    let ttl = T::PendingBlocksTtl::get();
459
32
                    PendingBlocks::<T>::insert(para_id, now.saturating_add(ttl));
460
32
                }
461
44
                Response::DispatchResult(_) => {
462
44
                    // We do not add paraid to pending block on failure
463
44
                }
464
                _ => {
465
                    // Unexpected.
466
                    return Err(Error::<T>::UnexpectedXCMResponse.into());
467
                }
468
            }
469

            
470
76
            Self::deposit_event(Event::ReceivedBuyCoreXCMResult { para_id, response });
471
76

            
472
76
            Ok(())
473
        }
474

            
475
        #[pallet::call_index(5)]
476
        #[pallet::weight(T::WeightInfo::clean_up_expired_in_flight_orders(expired_pending_blocks_para_id.len() as u32))]
477
        pub fn clean_up_expired_pending_blocks(
478
            origin: OriginFor<T>,
479
            expired_pending_blocks_para_id: Vec<ParaId>,
480
2
        ) -> DispatchResult {
481
2
            let _ = ensure_signed(origin)?;
482
2
            let now = frame_system::Pallet::<T>::block_number();
483
2
            let mut cleaned_up_para_ids = vec![];
484

            
485
4
            for para_id in expired_pending_blocks_para_id {
486
2
                let maybe_pending_block_ttl = PendingBlocks::<T>::get(para_id);
487
2
                if let Some(pending_block_ttl) = maybe_pending_block_ttl {
488
2
                    if pending_block_ttl < now {
489
1
                        PendingBlocks::<T>::remove(para_id);
490
1
                        cleaned_up_para_ids.push(para_id);
491
1
                    } else {
492
1
                        // Ignore if not expired
493
1
                    }
494
                }
495
            }
496

            
497
2
            Self::deposit_event(Event::CleanedUpExpiredPendingBlocksEntries {
498
2
                para_ids: cleaned_up_para_ids,
499
2
            });
500
2

            
501
2
            Ok(())
502
        }
503

            
504
        #[pallet::call_index(6)]
505
        #[pallet::weight(T::WeightInfo::clean_up_expired_in_flight_orders(expired_in_flight_orders.len() as u32))]
506
        pub fn clean_up_expired_in_flight_orders(
507
            origin: OriginFor<T>,
508
            expired_in_flight_orders: Vec<ParaId>,
509
2
        ) -> DispatchResult {
510
2
            let _ = ensure_signed(origin)?;
511
2
            let now = frame_system::Pallet::<T>::block_number();
512
2
            let mut cleaned_up_para_ids = vec![];
513

            
514
4
            for para_id in expired_in_flight_orders {
515
2
                let maybe_in_flight_order = InFlightOrders::<T>::get(para_id);
516
2
                if let Some(in_flight_order) = maybe_in_flight_order {
517
2
                    if in_flight_order.ttl < now {
518
1
                        InFlightOrders::<T>::remove(para_id);
519
1
                        QueryIdToParaId::<T>::remove(in_flight_order.query_id);
520
1
                        cleaned_up_para_ids.push(para_id);
521
1
                    } else {
522
1
                        // Ignore if not expired
523
1
                    }
524
                }
525
            }
526

            
527
2
            Self::deposit_event(Event::CleanedUpExpiredInFlightOrderEntries {
528
2
                para_ids: cleaned_up_para_ids,
529
2
            });
530
2

            
531
2
            Ok(())
532
        }
533
    }
534

            
535
    impl<T: Config> Pallet<T> {
536
        /// Returns the interior multilocation for this container chain para id. This is a relative
537
        /// multilocation that can be used in the `descend_origin` XCM opcode.
538
69
        pub fn interior_multilocation(para_id: ParaId) -> InteriorLocation {
539
69
            let container_chain_account = T::GetParathreadAccountId::convert(para_id);
540
69
            let account_junction = Junction::AccountId32 {
541
69
                id: container_chain_account,
542
69
                network: None,
543
69
            };
544
69

            
545
69
            [account_junction].into()
546
69
        }
547

            
548
        /// Returns a multilocation that can be used in the `deposit_asset` XCM opcode.
549
        /// The `interior_multilocation` can be obtained using `Self::interior_multilocation`.
550
69
        pub fn relay_relative_multilocation(
551
69
            interior_multilocation: InteriorLocation,
552
69
        ) -> Result<Location, Error<T>> {
553
69
            let relay_chain = Location::parent();
554
69
            let context: InteriorLocation = [Parachain(T::SelfParaId::get().into())].into();
555
69
            let mut reanchored: Location = interior_multilocation.into();
556
69
            reanchored
557
69
                .reanchor(&relay_chain, &context)
558
69
                .map_err(|_| Error::<T>::ReanchorFailed)?;
559

            
560
69
            Ok(reanchored)
561
69
        }
562

            
563
55
        pub fn is_core_buying_allowed(
564
55
            para_id: ParaId,
565
55
            _maybe_collator_public_key: Option<<T as Config>::CollatorPublicKey>,
566
55
        ) -> Result<(), BuyingError<BlockNumberFor<T>>> {
567
55
            // If an in flight order is pending (i.e we did not receive the notification yet) and our
568
55
            // record is not expired yet, we should not allow the collator to buy another core.
569
55
            let maybe_in_flight_order = InFlightOrders::<T>::get(para_id);
570
55
            if let Some(in_flight_order) = maybe_in_flight_order {
571
12
                if in_flight_order.ttl < <frame_system::Pallet<T>>::block_number() {
572
7
                    InFlightOrders::<T>::remove(para_id);
573
7
                } else {
574
5
                    return Err(BuyingError::OrderAlreadyExists {
575
5
                        ttl: in_flight_order.ttl,
576
5
                        current_block_number: <frame_system::Pallet<T>>::block_number(),
577
5
                    });
578
                }
579
43
            }
580

            
581
            // If a block production is pending and our record is not expired yet, we should not allow
582
            // the collator to buy another core yet.
583
50
            let maybe_pending_blocks_ttl = PendingBlocks::<T>::get(para_id);
584
50
            if let Some(pending_blocks_ttl) = maybe_pending_blocks_ttl {
585
3
                if pending_blocks_ttl < <frame_system::Pallet<T>>::block_number() {
586
1
                    PendingBlocks::<T>::remove(para_id);
587
1
                } else {
588
2
                    return Err(BuyingError::BlockProductionPending {
589
2
                        ttl: pending_blocks_ttl,
590
2
                        current_block_number: <frame_system::Pallet<T>>::block_number(),
591
2
                    });
592
                }
593
47
            }
594

            
595
            // Check that the para id is a parathread
596
48
            let parathread_params = T::GetParathreadParams::get_parathread_params(para_id)
597
48
                .ok_or(BuyingError::NotAParathread)?;
598

            
599
47
            let maybe_latest_author_info =
600
47
                T::LatestAuthorInfoFetcher::get_latest_author_info(para_id);
601
47
            if let Some(latest_author_info) = maybe_latest_author_info {
602
33
                let current_slot = T::SlotBeacon::slot();
603
33
                if !parathread_params.slot_frequency.should_parathread_buy_core(
604
33
                    Slot::from(current_slot as u64),
605
33
                    T::BuyCoreSlotDrift::get(),
606
33
                    latest_author_info.latest_slot_number,
607
33
                ) {
608
                    // TODO: Take max slots to produce a block from config
609
2
                    return Err(BuyingError::NotAllowedToProduceBlockRightNow {
610
2
                        slot_frequency: parathread_params.slot_frequency,
611
2
                        max_slot_earlier_core_buying_permitted: Slot::from(2u64),
612
2
                        last_block_production_slot: latest_author_info.latest_slot_number,
613
2
                    });
614
31
                }
615
14
            }
616

            
617
45
            Ok(())
618
55
        }
619

            
620
        /// Send an XCM message to the relay chain to try to buy a core for this para_id.
621
55
        fn on_collator_instantaneous_core_requested(
622
55
            para_id: ParaId,
623
55
            maybe_collator_public_key: Option<<T as Config>::CollatorPublicKey>,
624
55
        ) -> DispatchResult {
625
55
            Self::is_core_buying_allowed(para_id, maybe_collator_public_key)
626
55
                .map_err(Into::<Error<T>>::into)?;
627

            
628
44
            let xcm_weights_storage =
629
45
                RelayXcmWeightConfig::<T>::get().ok_or(Error::<T>::XcmWeightStorageNotSet)?;
630

            
631
44
            let withdraw_amount = xcm_weights_storage.buy_execution_cost;
632
44

            
633
44
            // Use the account derived from the multilocation composed with DescendOrigin
634
44
            // Buy on-demand cores
635
44
            // Any failure should return everything to the derivative account
636
44

            
637
44
            // Don't use utility::as_derivative because that will make the tanssi sovereign account
638
44
            // pay for fees, instead use `DescendOrigin` to make the parathread tank account
639
44
            // pay for fees.
640
44
            // TODO: when coretime is implemented, use coretime instantaneous credits instead of
641
44
            // buying on-demand cores at the price defined by the relay
642
44
            let origin = OriginKind::SovereignAccount;
643
44
            // TODO: max_amount is the max price of a core that this parathread is willing to pay
644
44
            // It should be defined in a storage item somewhere, controllable by the container chain
645
44
            // manager.
646
44
            let max_amount =
647
44
                T::GetParathreadMaxCorePrice::get_max_core_price(para_id).unwrap_or(u128::MAX);
648
44
            let call =
649
44
                T::GetPurchaseCoreCall::get_encoded(RelayChain::<T>::get(), max_amount, para_id);
650
44
            let weight_at_most = xcm_weights_storage.weight_at_most;
651
44

            
652
44
            // Assumption: derived account already has DOT
653
44
            // The balance should be enough to cover the `Withdraw` needed to `BuyExecution`, plus
654
44
            // the price of the core, which can change based on demand.
655
44
            let relay_asset_total: Asset = (Here, withdraw_amount).into();
656
44
            let refund_asset_filter: AssetFilter = AssetFilter::Wild(WildAsset::AllCounted(1));
657
44

            
658
44
            let interior_multilocation = Self::interior_multilocation(para_id);
659
            // The parathread tank account is derived from the tanssi sovereign account and the
660
            // parathread para id.
661
44
            let derived_account =
662
44
                Self::relay_relative_multilocation(interior_multilocation.clone())?;
663

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

            
669
44
            let notify_call = <T as Config>::RuntimeCall::from(Call::<T>::query_response {
670
44
                query_id: 0,
671
44
                response: Default::default(),
672
44
            });
673
44
            let notify_call_weight = notify_call.get_dispatch_info().call_weight;
674
44

            
675
44
            let notify_query_ttl = <frame_system::Pallet<T>>::block_number()
676
44
                .saturating_add(T::CoreBuyingXCMQueryTtl::get());
677
44

            
678
44
            // Send XCM to relay chain
679
44
            let relay_chain = Location::parent();
680
44
            let query_id = T::XCMNotifier::new_notify_query(
681
44
                relay_chain.clone(),
682
44
                notify_call,
683
44
                notify_query_ttl,
684
44
                interior_multilocation.clone(),
685
44
            );
686

            
687
44
            let message: Xcm<()> = Xcm::builder_unsafe()
688
44
                .descend_origin(interior_multilocation.clone())
689
44
                .withdraw_asset(Assets::from(vec![relay_asset_total.clone()]))
690
44
                .buy_execution(relay_asset_total, Unlimited)
691
44
                // Both in case of error and in case of success, we want to refund the unused weight
692
44
                .set_appendix(
693
44
                    Xcm::builder_unsafe()
694
44
                        .report_transact_status(QueryResponseInfo {
695
44
                            destination: T::UniversalLocation::get()
696
44
                                .invert_target(&relay_chain)
697
44
                                .map_err(|_| Error::<T>::LocationInversionFailed)?, // This location from the point of view of destination
698
44
                            query_id,
699
44
                            max_weight: notify_call_weight,
700
44
                        })
701
44
                        .refund_surplus()
702
44
                        .deposit_asset(refund_asset_filter, derived_account)
703
44
                        .build(),
704
44
                )
705
44
                .transact(origin, weight_at_most, call)
706
44
                .build();
707

            
708
            // We intentionally do not charge any fees
709
44
            let (ticket, _price) =
710
44
                T::XcmSender::validate(&mut Some(relay_chain), &mut Some(message))
711
44
                    .map_err(|_| Error::<T>::ErrorValidatingXCM)?;
712
44
            T::XcmSender::deliver(ticket).map_err(|_| Error::<T>::ErrorDeliveringXCM)?;
713
44
            Self::deposit_event(Event::BuyCoreXcmSent {
714
44
                para_id,
715
44
                transaction_status_query_id: query_id,
716
44
            });
717
44

            
718
44
            let in_flight_order_ttl =
719
44
                notify_query_ttl.saturating_add(T::AdditionalTtlForInflightOrders::get());
720
44
            InFlightOrders::<T>::insert(
721
44
                para_id,
722
44
                InFlightCoreBuyingOrder {
723
44
                    para_id,
724
44
                    query_id,
725
44
                    ttl: in_flight_order_ttl,
726
44
                },
727
44
            );
728
44

            
729
44
            QueryIdToParaId::<T>::insert(query_id, para_id);
730
44

            
731
44
            Ok(())
732
55
        }
733

            
734
75
        pub fn para_deregistered(para_id: ParaId) {
735
            // If para is deregistered we need to clean up in flight order, query id mapping
736
75
            if let Some(in_flight_order) = InFlightOrders::<T>::take(para_id) {
737
1
                InFlightOrders::<T>::remove(para_id);
738
1
                QueryIdToParaId::<T>::remove(in_flight_order.query_id);
739
74
            }
740

            
741
            // We need to clean the pending block entry if any
742
75
            PendingBlocks::<T>::remove(para_id);
743
75
        }
744
    }
745

            
746
    #[pallet::validate_unsigned]
747
    impl<T: Config> ValidateUnsigned for Pallet<T> {
748
        type Call = Call<T>;
749

            
750
56
        fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
751
56
            if let Call::buy_core { para_id, proof } = call {
752
56
                let block_number = <frame_system::Pallet<T>>::block_number();
753
56

            
754
56
                let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
755
56
                if proof.nonce != current_nonce {
756
14
                    return InvalidTransaction::Call.into();
757
42
                }
758
42

            
759
42
                let is_valid_collator =
760
42
                    T::CheckCollatorValidity::is_valid_collator(*para_id, proof.public_key.clone());
761
42
                if !is_valid_collator {
762
3
                    return InvalidTransaction::Call.into();
763
39
                }
764
39

            
765
39
                if !proof.verify_signature(*para_id) {
766
8
                    return InvalidTransaction::Call.into();
767
31
                }
768
31

            
769
31
                ValidTransaction::with_tag_prefix("XcmCoreBuyer")
770
31
                    .priority(T::UnsignedPriority::get())
771
31
                    // TODO: tags
772
31
                    .and_provides((block_number, para_id))
773
31
                    //.and_provides((current_session, authority_id))
774
31
                    //.longevity(
775
31
                    //    TryInto::<u64>::try_into(
776
31
                    //       T::NextSessionRotation::average_session_length() / 2u32.into(),
777
31
                    //    )
778
31
                    //        .unwrap_or(64_u64),
779
31
                    //)
780
31
                    .longevity(64)
781
31
                    .propagate(true)
782
31
                    .build()
783
            } else {
784
                InvalidTransaction::Call.into()
785
            }
786
56
        }
787
    }
788
}
789

            
790
pub trait GetPurchaseCoreCall<RelayChain> {
791
    /// Get the encoded call to buy a core for this `para_id`, with this `max_amount`.
792
    /// Returns the encoded call and its estimated weight.
793
    fn get_encoded(relay_chain: RelayChain, max_amount: u128, para_id: ParaId) -> Vec<u8>;
794
}
795

            
796
pub trait CheckCollatorValidity<AccountId, PublicKey> {
797
    fn is_valid_collator(para_id: ParaId, public_key: PublicKey) -> bool;
798

            
799
    #[cfg(feature = "runtime-benchmarks")]
800
    fn set_valid_collator(para_id: ParaId, account_id: AccountId, public_key: PublicKey);
801
}
802

            
803
pub trait GetParathreadMaxCorePrice {
804
    fn get_max_core_price(para_id: ParaId) -> Option<u128>;
805
}
806

            
807
impl GetParathreadMaxCorePrice for () {
808
20
    fn get_max_core_price(_para_id: ParaId) -> Option<u128> {
809
20
        None
810
20
    }
811
}
812

            
813
pub trait GetParathreadParams {
814
    fn get_parathread_params(para_id: ParaId) -> Option<ParathreadParams>;
815

            
816
    #[cfg(feature = "runtime-benchmarks")]
817
    fn set_parathread_params(para_id: ParaId, parathread_params: Option<ParathreadParams>);
818
}
819

            
820
/// Use `into_account_truncating` to convert a `ParaId` into a `[u8; 32]`.
821
pub struct ParaIdIntoAccountTruncating;
822

            
823
impl Convert<ParaId, [u8; 32]> for ParaIdIntoAccountTruncating {
824
357
    fn convert(para_id: ParaId) -> [u8; 32] {
825
357
        // Derive a 32 byte account id for a parathread. Note that this is not the address of
826
357
        // the relay chain parathread tank, but that address is derived from this.
827
357
        let account: dp_core::AccountId = para_id.into_account_truncating();
828
357

            
829
357
        account.into()
830
357
    }
831
}