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
#[derive(
81
    RuntimeDebug,
82
    PartialEq,
83
    Eq,
84
    Encode,
85
    Decode,
86
    Clone,
87
1746
    TypeInfo,
88
    Serialize,
89
    Deserialize,
90
    MaxEncodedLen,
91
)]
92
pub struct InFlightCoreBuyingOrder<BN> {
93
    para_id: ParaId,
94
    query_id: QueryId,
95
    ttl: BN,
96
}
97

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

            
118
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
119
20205
    fn on_container_author_noted(
120
20205
        _author: &T::AccountId,
121
20205
        _block_number: BlockNumber,
122
20205
        para_id: ParaId,
123
20205
    ) -> Weight {
124
20205
        PendingBlocks::<T>::remove(para_id);
125
20205

            
126
20205
        T::DbWeight::get().writes(1)
127
20205
    }
128
}
129

            
130
20476
#[frame_support::pallet]
131
pub mod pallet {
132
    use {
133
        super::*,
134
        nimbus_primitives::SlotBeacon,
135
        pallet_xcm::ensure_response,
136
        sp_runtime::{app_crypto::AppCrypto, RuntimeAppPublic},
137
    };
138

            
139
41954
    #[pallet::pallet]
140
    pub struct Pallet<T>(PhantomData<T>);
141

            
142
    #[pallet::config]
143
    pub trait Config: frame_system::Config {
144
        /// Overarching event type.
145
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
146
        type Currency: Inspect<Self::AccountId> + Balanced<Self::AccountId>;
147

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

            
172
        /// Get the parathread params. Used to verify that the para id is a parathread.
173
        // TODO: and in the future to restrict the ability to buy a core depending on slot frequency
174
        type GetParathreadParams: GetParathreadParams;
175
        /// Validate if particular account id and public key pair belongs to a collator and the collator
176
        /// is selected to collate for particular para id.
177
        type CheckCollatorValidity: CheckCollatorValidity<Self::AccountId, Self::CollatorPublicKey>;
178
        /// A configuration for base priority of unsigned transactions.
179
        ///
180
        /// This is exposed so that it can be tuned for particular runtime, when
181
        /// multiple pallets send unsigned transactions.
182
        #[pallet::constant]
183
        type UnsignedPriority: Get<TransactionPriority>;
184

            
185
        /// TTL for pending blocks entry, which prevents anyone to submit another core buying xcm.
186
        #[pallet::constant]
187
        type PendingBlocksTtl: Get<BlockNumberFor<Self>>;
188

            
189
        /// TTL to be used in xcm's notify query
190
        #[pallet::constant]
191
        type CoreBuyingXCMQueryTtl: Get<BlockNumberFor<Self>>;
192

            
193
        /// Additional ttl for in flight orders (total would be CoreBuyingXCMQueryTtl + AdditionalTtlForInflightOrders)
194
        /// after which the in flight orders can be cleaned up by anyone.
195
        #[pallet::constant]
196
        type AdditionalTtlForInflightOrders: Get<BlockNumberFor<Self>>;
197

            
198
        /// Slot drift allowed for core buying
199
        #[pallet::constant]
200
        type BuyCoreSlotDrift: Get<Slot>;
201

            
202
        #[pallet::constant]
203
        type UniversalLocation: Get<InteriorLocation>;
204

            
205
        type RuntimeOrigin: Into<Result<pallet_xcm::Origin, <Self as Config>::RuntimeOrigin>>
206
            + From<<Self as frame_system::Config>::RuntimeOrigin>;
207

            
208
        /// The overarching call type
209
        type RuntimeCall: From<Call<Self>> + Encode + GetDispatchInfo;
210

            
211
        /// Outcome notifier implements functionality to enable reporting back the outcome
212
        type XCMNotifier: XCMNotifier<Self>;
213

            
214
        type LatestAuthorInfoFetcher: LatestAuthorInfoFetcher<Self::AccountId>;
215

            
216
        type SlotBeacon: SlotBeacon;
217

            
218
        /// A PublicKey can be converted into an `AccountId`. This is required in order to verify
219
        /// the collator signature
220
        type CollatorPublicKey: Member
221
            + Parameter
222
            + RuntimeAppPublic
223
            + AppCrypto
224
            + MaybeSerializeDeserialize
225
            + MaxEncodedLen;
226

            
227
        type WeightInfo: WeightInfo;
228
    }
229

            
230
    #[pallet::event]
231
64
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
232
    pub enum Event<T: Config> {
233
50
        /// An XCM message to buy a core for this parathread has been sent to the relay chain.
234
        BuyCoreXcmSent {
235
            para_id: ParaId,
236
            transaction_status_query_id: QueryId,
237
        },
238
21
        /// We received response for xcm
239
        ReceivedBuyCoreXCMResult { para_id: ParaId, response: Response },
240

            
241
2
        /// We cleaned up expired pending blocks entries.
242
        CleanedUpExpiredPendingBlocksEntries { para_ids: Vec<ParaId> },
243

            
244
2
        /// We cleaned up expired in flight orders entries.
245
        CleanedUpExpiredInFlightOrderEntries { para_ids: Vec<ParaId> },
246
    }
247

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

            
285
    impl<T: Config> From<BuyingError<BlockNumberFor<T>>> for Error<T> {
286
10
        fn from(value: BuyingError<BlockNumberFor<T>>) -> Self {
287
10
            match value {
288
5
                BuyingError::OrderAlreadyExists { .. } => Error::<T>::OrderAlreadyExists,
289
2
                BuyingError::BlockProductionPending { .. } => Error::<T>::BlockProductionPending,
290
1
                BuyingError::NotAParathread => Error::<T>::NotAParathread,
291
                BuyingError::NotAllowedToProduceBlockRightNow { .. } => {
292
2
                    Error::<T>::NotAllowedToProduceBlockRightNow
293
                }
294
            }
295
10
        }
296
    }
297

            
298
    /// Set of parathreads that have already sent an XCM message to buy a core recently.
299
    /// Used to avoid 2 collators buying a core at the same time, because it is only possible to buy
300
    /// 1 core in 1 relay block for the same parathread.
301
205
    #[pallet::storage]
302
    pub type InFlightOrders<T: Config> =
303
        StorageMap<_, Twox128, ParaId, InFlightCoreBuyingOrder<BlockNumberFor<T>>, OptionQuery>;
304

            
305
    /// Number of pending blocks
306
20343
    #[pallet::storage]
307
    pub type PendingBlocks<T: Config> =
308
        StorageMap<_, Twox128, ParaId, BlockNumberFor<T>, OptionQuery>;
309

            
310
    /// Mapping of QueryId to ParaId
311
99
    #[pallet::storage]
312
    pub type QueryIdToParaId<T: Config> = StorageMap<_, Twox128, QueryId, ParaId, OptionQuery>;
313

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

            
321
    /// Collator signature nonce for reply protection
322
106
    #[pallet::storage]
323
    pub type CollatorSignatureNonce<T: Config> = StorageMap<_, Twox128, ParaId, u64, ValueQuery>;
324

            
325
    #[derive(
326
1746
        Encode, Decode, CloneNoBound, PartialEq, Eq, DebugNoBound, TypeInfo, MaxEncodedLen,
327
    )]
328
    #[scale_info(skip_type_params(T))]
329
    pub struct RelayXcmWeightConfigInner<T> {
330
        pub buy_execution_cost: u128,
331
        pub weight_at_most: Weight,
332
        pub _phantom: PhantomData<T>,
333
    }
334

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

            
341
256
    #[pallet::call]
342
    impl<T: Config> Pallet<T> {
343
        /// Buy a core for this parathread id.
344
        /// Collators should call this to indicate that they intend to produce a block, but they
345
        /// cannot do it because this para id has no available cores.
346
        /// The purchase is automatic using XCM, and collators do not need to do anything.
347
        // Note that the collators that will be calling this function are parathread collators, not
348
        // tanssi collators. So we cannot force them to provide a complex proof, e.g. against relay
349
        // state.
350
        #[pallet::call_index(0)]
351
        #[pallet::weight(T::WeightInfo::buy_core())]
352
        pub fn buy_core(
353
            origin: OriginFor<T>,
354
            para_id: ParaId,
355
            // Below parameter are already validated during `validate_unsigned` cal
356
            proof: BuyCoreCollatorProof<T::CollatorPublicKey>,
357
20
        ) -> DispatchResult {
358
20
            ensure_none(origin)?;
359

            
360
20
            let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
361
20
            CollatorSignatureNonce::<T>::set(para_id, current_nonce + 1);
362
20

            
363
20
            Self::on_collator_instantaneous_core_requested(para_id, Some(proof.public_key))
364
        }
365

            
366
        /// Buy core for para id as root. Does not require any proof, useful in tests.
367
        #[pallet::call_index(1)]
368
        #[pallet::weight(T::WeightInfo::force_buy_core())]
369
36
        pub fn force_buy_core(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
370
36
            ensure_root(origin)?;
371

            
372
35
            Self::on_collator_instantaneous_core_requested(para_id, None)
373
        }
374

            
375
        #[pallet::call_index(2)]
376
        #[pallet::weight(T::WeightInfo::set_relay_xcm_weight_config())]
377
        pub fn set_relay_xcm_weight_config(
378
            origin: OriginFor<T>,
379
            xcm_weights: Option<RelayXcmWeightConfigInner<T>>,
380
36
        ) -> DispatchResult {
381
36
            ensure_root(origin)?;
382

            
383
36
            if let Some(xcm_weights) = xcm_weights {
384
35
                RelayXcmWeightConfig::<T>::put(xcm_weights);
385
35
            } else {
386
1
                RelayXcmWeightConfig::<T>::kill();
387
1
            }
388

            
389
36
            Ok(())
390
        }
391

            
392
        #[pallet::call_index(3)]
393
        #[pallet::weight(T::WeightInfo::set_relay_chain())]
394
        pub fn set_relay_chain(
395
            origin: OriginFor<T>,
396
            relay_chain: Option<T::RelayChain>,
397
12
        ) -> DispatchResult {
398
12
            ensure_root(origin)?;
399

            
400
12
            if let Some(relay_chain) = relay_chain {
401
12
                RelayChain::<T>::put(relay_chain);
402
12
            } else {
403
                RelayChain::<T>::kill();
404
            }
405

            
406
12
            Ok(())
407
        }
408

            
409
        #[pallet::call_index(4)]
410
        #[pallet::weight(T::WeightInfo::query_response())]
411
        pub fn query_response(
412
            origin: OriginFor<T>,
413
            query_id: QueryId,
414
            response: Response,
415
16
        ) -> DispatchResult {
416
16
            let _responder = ensure_response(<T as Config>::RuntimeOrigin::from(origin))?;
417

            
418
16
            let maybe_para_id = QueryIdToParaId::<T>::get(query_id);
419

            
420
16
            let para_id = if let Some(para_id) = maybe_para_id {
421
16
                para_id
422
            } else {
423
                // Most probably entry was expired or removed in some other way. Let's return early.
424
                return Ok(());
425
            };
426

            
427
16
            QueryIdToParaId::<T>::remove(query_id);
428
16
            InFlightOrders::<T>::remove(para_id);
429

            
430
16
            match response {
431
8
                Response::DispatchResult(MaybeErrorCode::Success) => {
432
8
                    // Success. Add para id to pending block
433
8
                    let now = <frame_system::Pallet<T>>::block_number();
434
8
                    let ttl = T::PendingBlocksTtl::get();
435
8
                    PendingBlocks::<T>::insert(para_id, now + ttl);
436
8
                }
437
8
                Response::DispatchResult(_) => {
438
8
                    // We do not add paraid to pending block on failure
439
8
                }
440
                _ => {
441
                    // Unexpected.
442
                    return Err(Error::<T>::UnexpectedXCMResponse.into());
443
                }
444
            }
445

            
446
16
            Self::deposit_event(Event::ReceivedBuyCoreXCMResult { para_id, response });
447
16

            
448
16
            Ok(())
449
        }
450

            
451
        #[pallet::call_index(5)]
452
        #[pallet::weight(T::WeightInfo::clean_up_expired_in_flight_orders(expired_pending_blocks_para_id.len() as u32))]
453
        pub fn clean_up_expired_pending_blocks(
454
            origin: OriginFor<T>,
455
            expired_pending_blocks_para_id: Vec<ParaId>,
456
2
        ) -> DispatchResult {
457
2
            let _ = ensure_signed(origin)?;
458
2
            let now = frame_system::Pallet::<T>::block_number();
459
2
            let mut cleaned_up_para_ids = vec![];
460

            
461
4
            for para_id in expired_pending_blocks_para_id {
462
2
                let maybe_pending_block_ttl = PendingBlocks::<T>::get(para_id);
463
2
                if let Some(pending_block_ttl) = maybe_pending_block_ttl {
464
2
                    if pending_block_ttl < now {
465
1
                        PendingBlocks::<T>::remove(para_id);
466
1
                        cleaned_up_para_ids.push(para_id);
467
1
                    } else {
468
1
                        // Ignore if not expired
469
1
                    }
470
                }
471
            }
472

            
473
2
            Self::deposit_event(Event::CleanedUpExpiredPendingBlocksEntries {
474
2
                para_ids: cleaned_up_para_ids,
475
2
            });
476
2

            
477
2
            Ok(())
478
        }
479

            
480
        #[pallet::call_index(6)]
481
        #[pallet::weight(T::WeightInfo::clean_up_expired_in_flight_orders(expired_in_flight_orders.len() as u32))]
482
        pub fn clean_up_expired_in_flight_orders(
483
            origin: OriginFor<T>,
484
            expired_in_flight_orders: Vec<ParaId>,
485
2
        ) -> DispatchResult {
486
2
            let _ = ensure_signed(origin)?;
487
2
            let now = frame_system::Pallet::<T>::block_number();
488
2
            let mut cleaned_up_para_ids = vec![];
489

            
490
4
            for para_id in expired_in_flight_orders {
491
2
                let maybe_in_flight_order = InFlightOrders::<T>::get(para_id);
492
2
                if let Some(in_flight_order) = maybe_in_flight_order {
493
2
                    if in_flight_order.ttl < now {
494
1
                        InFlightOrders::<T>::remove(para_id);
495
1
                        QueryIdToParaId::<T>::remove(in_flight_order.query_id);
496
1
                        cleaned_up_para_ids.push(para_id);
497
1
                    } else {
498
1
                        // Ignore if not expired
499
1
                    }
500
                }
501
            }
502

            
503
2
            Self::deposit_event(Event::CleanedUpExpiredInFlightOrderEntries {
504
2
                para_ids: cleaned_up_para_ids,
505
2
            });
506
2

            
507
2
            Ok(())
508
        }
509
    }
510

            
511
    impl<T: Config> Pallet<T> {
512
        /// Returns the interior multilocation for this container chain para id. This is a relative
513
        /// multilocation that can be used in the `descend_origin` XCM opcode.
514
69
        pub fn interior_multilocation(para_id: ParaId) -> InteriorLocation {
515
69
            let container_chain_account = T::GetParathreadAccountId::convert(para_id);
516
69
            let account_junction = Junction::AccountId32 {
517
69
                id: container_chain_account,
518
69
                network: None,
519
69
            };
520
69

            
521
69
            [account_junction].into()
522
69
        }
523

            
524
        /// Returns a multilocation that can be used in the `deposit_asset` XCM opcode.
525
        /// The `interior_multilocation` can be obtained using `Self::interior_multilocation`.
526
69
        pub fn relay_relative_multilocation(
527
69
            interior_multilocation: InteriorLocation,
528
69
        ) -> Result<Location, Error<T>> {
529
69
            let relay_chain = Location::parent();
530
69
            let context: InteriorLocation = [Parachain(T::SelfParaId::get().into())].into();
531
69
            let mut reanchored: Location = interior_multilocation.into();
532
69
            reanchored
533
69
                .reanchor(&relay_chain, &context)
534
69
                .map_err(|_| Error::<T>::ReanchorFailed)?;
535

            
536
69
            Ok(reanchored)
537
69
        }
538

            
539
55
        pub fn is_core_buying_allowed(
540
55
            para_id: ParaId,
541
55
            _maybe_collator_public_key: Option<<T as Config>::CollatorPublicKey>,
542
55
        ) -> Result<(), BuyingError<BlockNumberFor<T>>> {
543
55
            // If an in flight order is pending (i.e we did not receive the notification yet) and our
544
55
            // record is not expired yet, we should not allow the collator to buy another core.
545
55
            let maybe_in_flight_order = InFlightOrders::<T>::get(para_id);
546
55
            if let Some(in_flight_order) = maybe_in_flight_order {
547
12
                if in_flight_order.ttl < <frame_system::Pallet<T>>::block_number() {
548
7
                    InFlightOrders::<T>::remove(para_id);
549
7
                } else {
550
5
                    return Err(BuyingError::OrderAlreadyExists {
551
5
                        ttl: in_flight_order.ttl,
552
5
                        current_block_number: <frame_system::Pallet<T>>::block_number(),
553
5
                    });
554
                }
555
43
            }
556

            
557
            // If a block production is pending and our record is not expired yet, we should not allow
558
            // the collator to buy another core yet.
559
50
            let maybe_pending_blocks_ttl = PendingBlocks::<T>::get(para_id);
560
50
            if let Some(pending_blocks_ttl) = maybe_pending_blocks_ttl {
561
3
                if pending_blocks_ttl < <frame_system::Pallet<T>>::block_number() {
562
1
                    PendingBlocks::<T>::remove(para_id);
563
1
                } else {
564
2
                    return Err(BuyingError::BlockProductionPending {
565
2
                        ttl: pending_blocks_ttl,
566
2
                        current_block_number: <frame_system::Pallet<T>>::block_number(),
567
2
                    });
568
                }
569
47
            }
570

            
571
            // Check that the para id is a parathread
572
48
            let parathread_params = T::GetParathreadParams::get_parathread_params(para_id)
573
48
                .ok_or(BuyingError::NotAParathread)?;
574

            
575
47
            let maybe_latest_author_info =
576
47
                T::LatestAuthorInfoFetcher::get_latest_author_info(para_id);
577
47
            if let Some(latest_author_info) = maybe_latest_author_info {
578
33
                let current_slot = T::SlotBeacon::slot();
579
33
                if !parathread_params.slot_frequency.should_parathread_buy_core(
580
33
                    Slot::from(current_slot as u64),
581
33
                    T::BuyCoreSlotDrift::get(),
582
33
                    latest_author_info.latest_slot_number,
583
33
                ) {
584
                    // TODO: Take max slots to produce a block from config
585
2
                    return Err(BuyingError::NotAllowedToProduceBlockRightNow {
586
2
                        slot_frequency: parathread_params.slot_frequency,
587
2
                        max_slot_earlier_core_buying_permitted: Slot::from(2u64),
588
2
                        last_block_production_slot: latest_author_info.latest_slot_number,
589
2
                    });
590
31
                }
591
14
            }
592

            
593
45
            Ok(())
594
55
        }
595

            
596
        /// Send an XCM message to the relay chain to try to buy a core for this para_id.
597
55
        fn on_collator_instantaneous_core_requested(
598
55
            para_id: ParaId,
599
55
            maybe_collator_public_key: Option<<T as Config>::CollatorPublicKey>,
600
55
        ) -> DispatchResult {
601
55
            Self::is_core_buying_allowed(para_id, maybe_collator_public_key)
602
55
                .map_err(Into::<Error<T>>::into)?;
603

            
604
44
            let xcm_weights_storage =
605
45
                RelayXcmWeightConfig::<T>::get().ok_or(Error::<T>::XcmWeightStorageNotSet)?;
606

            
607
44
            let withdraw_amount = xcm_weights_storage.buy_execution_cost;
608
44

            
609
44
            // Use the account derived from the multilocation composed with DescendOrigin
610
44
            // Buy on-demand cores
611
44
            // Any failure should return everything to the derivative account
612
44

            
613
44
            // Don't use utility::as_derivative because that will make the tanssi sovereign account
614
44
            // pay for fees, instead use `DescendOrigin` to make the parathread tank account
615
44
            // pay for fees.
616
44
            // TODO: when coretime is implemented, use coretime instantaneous credits instead of
617
44
            // buying on-demand cores at the price defined by the relay
618
44
            let origin = OriginKind::SovereignAccount;
619
44
            // TODO: max_amount is the max price of a core that this parathread is willing to pay
620
44
            // It should be defined in a storage item somewhere, controllable by the container chain
621
44
            // manager.
622
44
            let max_amount =
623
44
                T::GetParathreadMaxCorePrice::get_max_core_price(para_id).unwrap_or(u128::MAX);
624
44
            let call =
625
44
                T::GetPurchaseCoreCall::get_encoded(RelayChain::<T>::get(), max_amount, para_id);
626
44
            let weight_at_most = xcm_weights_storage.weight_at_most;
627
44

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

            
634
44
            let interior_multilocation = Self::interior_multilocation(para_id);
635
            // The parathread tank account is derived from the tanssi sovereign account and the
636
            // parathread para id.
637
44
            let derived_account =
638
44
                Self::relay_relative_multilocation(interior_multilocation.clone())?;
639

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

            
645
44
            let notify_call = <T as Config>::RuntimeCall::from(Call::<T>::query_response {
646
44
                query_id: 0,
647
44
                response: Default::default(),
648
44
            });
649
44
            let notify_call_weight = notify_call.get_dispatch_info().weight;
650
44

            
651
44
            let notify_query_ttl =
652
44
                <frame_system::Pallet<T>>::block_number() + T::CoreBuyingXCMQueryTtl::get();
653
44

            
654
44
            // Send XCM to relay chain
655
44
            let relay_chain = Location::parent();
656
44
            let query_id = T::XCMNotifier::new_notify_query(
657
44
                relay_chain.clone(),
658
44
                notify_call,
659
44
                notify_query_ttl,
660
44
                interior_multilocation.clone(),
661
44
            );
662

            
663
44
            let message: Xcm<()> = Xcm::builder_unsafe()
664
44
                .descend_origin(interior_multilocation.clone())
665
44
                .withdraw_asset(Assets::from(vec![relay_asset_total.clone()]))
666
44
                .buy_execution(relay_asset_total, Unlimited)
667
44
                // Both in case of error and in case of success, we want to refund the unused weight
668
44
                .set_appendix(
669
44
                    Xcm::builder_unsafe()
670
44
                        .report_transact_status(QueryResponseInfo {
671
44
                            destination: T::UniversalLocation::get()
672
44
                                .invert_target(&relay_chain)
673
44
                                .map_err(|_| Error::<T>::LocationInversionFailed)?, // This location from the point of view of destination
674
44
                            query_id,
675
44
                            max_weight: notify_call_weight,
676
44
                        })
677
44
                        .refund_surplus()
678
44
                        .deposit_asset(refund_asset_filter, derived_account)
679
44
                        .build(),
680
44
                )
681
44
                .transact(origin, weight_at_most, call)
682
44
                .build();
683

            
684
            // We intentionally do not charge any fees
685
44
            let (ticket, _price) =
686
44
                T::XcmSender::validate(&mut Some(relay_chain), &mut Some(message))
687
44
                    .map_err(|_| Error::<T>::ErrorValidatingXCM)?;
688
44
            T::XcmSender::deliver(ticket).map_err(|_| Error::<T>::ErrorDeliveringXCM)?;
689
44
            Self::deposit_event(Event::BuyCoreXcmSent {
690
44
                para_id,
691
44
                transaction_status_query_id: query_id,
692
44
            });
693
44

            
694
44
            let in_flight_order_ttl = notify_query_ttl + T::AdditionalTtlForInflightOrders::get();
695
44
            InFlightOrders::<T>::insert(
696
44
                para_id,
697
44
                InFlightCoreBuyingOrder {
698
44
                    para_id,
699
44
                    query_id,
700
44
                    ttl: in_flight_order_ttl,
701
44
                },
702
44
            );
703
44

            
704
44
            QueryIdToParaId::<T>::insert(query_id, para_id);
705
44

            
706
44
            Ok(())
707
55
        }
708

            
709
57
        pub fn para_deregistered(para_id: ParaId) {
710
            // If para is deregistered we need to clean up in flight order, query id mapping
711
57
            if let Some(in_flight_order) = InFlightOrders::<T>::take(para_id) {
712
1
                InFlightOrders::<T>::remove(para_id);
713
1
                QueryIdToParaId::<T>::remove(in_flight_order.query_id);
714
56
            }
715

            
716
            // We need to clean the pending block entry if any
717
57
            PendingBlocks::<T>::remove(para_id);
718
57
        }
719
    }
720

            
721
    #[pallet::validate_unsigned]
722
    impl<T: Config> ValidateUnsigned for Pallet<T> {
723
        type Call = Call<T>;
724

            
725
56
        fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
726
56
            if let Call::buy_core { para_id, proof } = call {
727
56
                let block_number = <frame_system::Pallet<T>>::block_number();
728
56

            
729
56
                let current_nonce = CollatorSignatureNonce::<T>::get(para_id);
730
56
                if proof.nonce != current_nonce {
731
14
                    return InvalidTransaction::Call.into();
732
42
                }
733
42

            
734
42
                let is_valid_collator =
735
42
                    T::CheckCollatorValidity::is_valid_collator(*para_id, proof.public_key.clone());
736
42
                if !is_valid_collator {
737
3
                    return InvalidTransaction::Call.into();
738
39
                }
739
39

            
740
39
                if !proof.verify_signature(*para_id) {
741
8
                    return InvalidTransaction::Call.into();
742
31
                }
743
31

            
744
31
                ValidTransaction::with_tag_prefix("XcmCoreBuyer")
745
31
                    .priority(T::UnsignedPriority::get())
746
31
                    // TODO: tags
747
31
                    .and_provides((block_number, para_id))
748
31
                    //.and_provides((current_session, authority_id))
749
31
                    //.longevity(
750
31
                    //    TryInto::<u64>::try_into(
751
31
                    //       T::NextSessionRotation::average_session_length() / 2u32.into(),
752
31
                    //    )
753
31
                    //        .unwrap_or(64_u64),
754
31
                    //)
755
31
                    .longevity(64)
756
31
                    .propagate(true)
757
31
                    .build()
758
            } else {
759
                InvalidTransaction::Call.into()
760
            }
761
56
        }
762
    }
763
}
764

            
765
pub trait GetPurchaseCoreCall<RelayChain> {
766
    /// Get the encoded call to buy a core for this `para_id`, with this `max_amount`.
767
    /// Returns the encoded call and its estimated weight.
768
    fn get_encoded(relay_chain: RelayChain, max_amount: u128, para_id: ParaId) -> Vec<u8>;
769
}
770

            
771
pub trait CheckCollatorValidity<AccountId, PublicKey> {
772
    fn is_valid_collator(para_id: ParaId, public_key: PublicKey) -> bool;
773

            
774
    #[cfg(feature = "runtime-benchmarks")]
775
    fn set_valid_collator(para_id: ParaId, account_id: AccountId, public_key: PublicKey);
776
}
777

            
778
pub trait GetParathreadMaxCorePrice {
779
    fn get_max_core_price(para_id: ParaId) -> Option<u128>;
780
}
781

            
782
impl GetParathreadMaxCorePrice for () {
783
20
    fn get_max_core_price(_para_id: ParaId) -> Option<u128> {
784
20
        None
785
20
    }
786
}
787

            
788
pub trait GetParathreadParams {
789
    fn get_parathread_params(para_id: ParaId) -> Option<ParathreadParams>;
790

            
791
    #[cfg(feature = "runtime-benchmarks")]
792
    fn set_parathread_params(para_id: ParaId, parathread_params: Option<ParathreadParams>);
793
}
794

            
795
/// Use `into_account_truncating` to convert a `ParaId` into a `[u8; 32]`.
796
pub struct ParaIdIntoAccountTruncating;
797

            
798
impl Convert<ParaId, [u8; 32]> for ParaIdIntoAccountTruncating {
799
249
    fn convert(para_id: ParaId) -> [u8; 32] {
800
249
        // Derive a 32 byte account id for a parathread. Note that this is not the address of
801
249
        // the relay chain parathread tank, but that address is derived from this.
802
249
        let account: dp_core::AccountId = para_id.into_account_truncating();
803
249

            
804
249
        account.into()
805
249
    }
806
}