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
//! # Author Noting Pallet
18
//!
19
//! This pallet notes the author of the different containerChains that have registered:
20
//!
21
//! The set of container chains is retrieved thanks to the GetContainerChains trait
22
//! For each containerChain, we inspect the Header stored in the relayChain as
23
//! a generic header. This is the first requirement for containerChains.
24
//!
25
//! The second requirement is that an Aura digest with the slot number for the containerChains
26
//! needs to exist
27
//!  
28
//! Using those two requirements we can select who the author was based on the collators assigned
29
//! to that containerChain, by simply assigning the slot position.
30

            
31
#![cfg_attr(not(feature = "std"), no_std)]
32
extern crate alloc;
33

            
34
use {
35
    alloc::borrow::Cow,
36
    alloc::vec::Vec,
37
    cumulus_pallet_parachain_system::RelaychainStateProvider,
38
    cumulus_primitives_core::{
39
        relay_chain::{BlakeTwo256, BlockNumber, HeadData},
40
        ParaId,
41
    },
42
    dp_core::well_known_keys::PARAS_HEADS_INDEX,
43
    frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*, Hashable},
44
    frame_system::pallet_prelude::*,
45
    nimbus_primitives::SlotBeacon,
46
    parity_scale_codec::{Decode, Encode},
47
    sp_consensus_aura::{inherents::InherentType, Slot, AURA_ENGINE_ID},
48
    sp_inherents::{InherentIdentifier, IsFatalError},
49
    sp_runtime::{traits::Header, DigestItem, DispatchResult},
50
    tp_author_noting_inherent::INHERENT_IDENTIFIER,
51
    tp_traits::{
52
        AuthorNotingHook, AuthorNotingInfo, ContainerChainBlockInfo, ForSession, GenericStateProof,
53
        GenericStorageReader, GetContainerChainAuthor, GetContainerChainsWithCollators,
54
        LatestAuthorInfoFetcher, NativeStorageReader, ReadEntryErr,
55
    },
56
};
57

            
58
#[cfg(test)]
59
mod mock;
60

            
61
#[cfg(test)]
62
mod tests;
63
pub mod weights;
64
pub use weights::WeightInfo;
65

            
66
#[cfg(any(test, feature = "runtime-benchmarks"))]
67
mod benchmarks;
68
#[cfg(feature = "runtime-benchmarks")]
69
mod mock_proof;
70

            
71
pub use pallet::*;
72

            
73
#[frame_support::pallet]
74
pub mod pallet {
75
    use super::*;
76

            
77
    #[pallet::config]
78
    pub trait Config: frame_system::Config {
79
        type ContainerChains: GetContainerChainsWithCollators<Self::AccountId>;
80

            
81
        type SlotBeacon: SlotBeacon;
82

            
83
        type ContainerChainAuthor: GetContainerChainAuthor<Self::AccountId>;
84

            
85
        /// An entry-point for higher-level logic to react to containers chains authoring.
86
        ///
87
        /// Typically, this can be a hook to reward block authors.
88
        type AuthorNotingHook: AuthorNotingHook<Self::AccountId>;
89

            
90
        type RelayOrPara: RelayOrPara;
91

            
92
        /// Max length of para id list, should be the same value as in other pallets.
93
        #[pallet::constant]
94
        type MaxContainerChains: Get<u32>;
95

            
96
        /// Weight information for extrinsics in this pallet.
97
        type WeightInfo: WeightInfo;
98
    }
99

            
100
    #[pallet::error]
101
    pub enum Error<T> {
102
        /// The new value for a configuration parameter is invalid.
103
        FailedReading,
104
        FailedDecodingHeader,
105
        AuraDigestFirstItem,
106
        AsPreRuntimeError,
107
        NonDecodableSlot,
108
        AuthorNotFound,
109
        NonAuraDigest,
110
    }
111

            
112
    #[pallet::pallet]
113
    pub struct Pallet<T>(PhantomData<T>);
114

            
115
    #[pallet::hooks]
116
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
117
153
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
118
153
            let mut weight = Weight::zero();
119

            
120
            // We clear this storage item to make sure its always included
121
153
            DidSetContainerAuthorData::<T>::kill();
122

            
123
153
            weight.saturating_accrue(T::DbWeight::get().writes(1));
124

            
125
            // The read onfinalizes
126
153
            weight.saturating_accrue(T::DbWeight::get().reads(1));
127

            
128
153
            weight
129
153
        }
130

            
131
149
        fn on_finalize(_: BlockNumberFor<T>) {
132
149
            assert!(
133
149
                <DidSetContainerAuthorData<T>>::exists(),
134
1
                "Container chain author data needs to be present in every block!"
135
            );
136
148
        }
137
    }
138

            
139
    #[pallet::call]
140
    impl<T: Config> Pallet<T> {
141
        #[pallet::call_index(0)]
142
        #[pallet::weight((T::WeightInfo::set_latest_author_data(T::MaxContainerChains::get()).saturating_add(T::WeightInfo::on_container_authors_noted(
143
            T::MaxContainerChains::get(),
144
        )), DispatchClass::Mandatory))]
145
        #[allow(clippy::useless_conversion)]
146
        pub fn set_latest_author_data(
147
            origin: OriginFor<T>,
148
            data: InherentDataOf<T>,
149
158
        ) -> DispatchResultWithPostInfo {
150
158
            ensure_none(origin)?;
151

            
152
158
            assert!(
153
158
                !<DidSetContainerAuthorData<T>>::exists(),
154
1
                "DidSetContainerAuthorData must be updated only once in a block",
155
            );
156

            
157
157
            let container_chains_to_check: Vec<_> =
158
157
                T::ContainerChains::container_chains_with_collators(ForSession::Current)
159
157
                    .into_iter()
160
203
                    .filter_map(|(para_id, collators)| (!collators.is_empty()).then_some(para_id))
161
157
                    .collect();
162
            // We have two benchmarks, one for `set_latest_author_data`, which disables hooks,
163
            // and one benchmark only for only hooks: `on_container_authors_noted`.
164
            // The weight hint needs to include both of them to account for the worst case.
165
            // The returned weight will be lower than the hint, because we never have
166
            // `T::MaxContainerChains::get()` chains registered.
167
            // Here we initialize the weight to the base weight, without hooks, but for the real
168
            // number of chains. Later we will add the weight reported by the hooks.
169
            // The weight used by hooks is not calculated using the `on_container_authors_noted`
170
            // benchmark, instead it is a manual guess of the number of storage reads and writes.
171
157
            let mut total_weight =
172
157
                T::WeightInfo::set_latest_author_data(container_chains_to_check.len() as u32);
173

            
174
            // We do this first to make sure we don't do 2 reads (parachains and relay state)
175
            // when we have no containers registered
176
            // Essentially one can pass an empty proof if no container-chains are registered
177
157
            if !container_chains_to_check.is_empty() {
178
146
                let storage_reader = T::RelayOrPara::create_storage_reader(data);
179

            
180
146
                let parent_tanssi_slot = u64::from(T::SlotBeacon::slot()).into();
181
146
                let mut infos = Vec::with_capacity(container_chains_to_check.len());
182

            
183
320
                for para_id in container_chains_to_check {
184
174
                    match Self::fetch_block_info_from_proof(
185
174
                        &storage_reader,
186
174
                        para_id,
187
174
                        parent_tanssi_slot,
188
174
                    ) {
189
150
                        Ok(block_info) => {
190
150
                            let _ = LatestAuthor::<T>::try_mutate(
191
150
                                para_id,
192
                                |maybe_old_block_info: &mut Option<
193
                                    ContainerChainBlockInfo<T::AccountId>,
194
151
                                >| {
195
                                    // No block number is the same as the last block number being 0:
196
                                    // the first block created by collators is block number 1.
197
151
                                    let old_block_number = maybe_old_block_info
198
151
                                        .as_ref()
199
151
                                        .map(|old_block_info| old_block_info.block_number)
200
151
                                        .unwrap_or(0);
201
                                    // We only reward author if the block increases
202
                                    // If there is no previous block, we should reward the author of the first block
203
151
                                    if block_info.block_number > old_block_number {
204
151
                                        let bi = block_info.clone();
205
151
                                        let info = AuthorNotingInfo {
206
151
                                            author: block_info.author,
207
151
                                            block_number: block_info.block_number,
208
151
                                            para_id,
209
151
                                        };
210
151
                                        infos.push(info);
211
151
                                        *maybe_old_block_info = Some(bi);
212
151
                                        Ok(())
213
                                    } else {
214
                                        // No need to mutate value if block number didn't change
215
                                        Err(())
216
                                    }
217
151
                                },
218
                            );
219
                        }
220
24
                        Err(e) => log::warn!(
221
                            "Author-noting error {:?} found in para {:?}",
222
                            e,
223
                            u32::from(para_id)
224
                        ),
225
                    }
226
                }
227

            
228
                // Call AuthorNotingHook
229
                // When benchmarking, we want the possibility to not call hooks, to get the base
230
                // weight of this inherent.
231
                #[cfg(feature = "runtime-benchmarks")]
232
                if crate::benchmarks::should_run_author_noting_hooks() {
233
                    total_weight
234
                        .saturating_accrue(T::AuthorNotingHook::on_container_authors_noted(&infos));
235
                }
236
                // not runtime-benchmarks: always call hook
237
                #[cfg(not(feature = "runtime-benchmarks"))]
238
146
                total_weight
239
146
                    .saturating_accrue(T::AuthorNotingHook::on_container_authors_noted(&infos));
240
11
            }
241

            
242
            // We correctly set the data
243
157
            DidSetContainerAuthorData::<T>::put(true);
244

            
245
157
            Ok(PostDispatchInfo {
246
157
                actual_weight: Some(total_weight),
247
157
                pays_fee: Pays::No,
248
157
            })
249
        }
250

            
251
        #[pallet::call_index(1)]
252
        #[pallet::weight(T::WeightInfo::set_author())]
253
        pub fn set_author(
254
            origin: OriginFor<T>,
255
            para_id: ParaId,
256
            block_number: BlockNumber,
257
            author: T::AccountId,
258
            latest_slot_number: Slot,
259
9
        ) -> DispatchResult {
260
9
            ensure_root(origin)?;
261
5
            LatestAuthor::<T>::insert(
262
5
                para_id,
263
5
                ContainerChainBlockInfo {
264
5
                    block_number,
265
5
                    author: author.clone(),
266
5
                    latest_slot_number,
267
5
                },
268
            );
269
5
            Self::deposit_event(Event::LatestAuthorChanged {
270
5
                para_id,
271
5
                block_number,
272
5
                new_author: author,
273
5
                latest_slot_number,
274
5
            });
275
5
            Ok(())
276
        }
277

            
278
        #[pallet::call_index(2)]
279
        #[pallet::weight(T::WeightInfo::kill_author_data())]
280
95
        pub fn kill_author_data(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
281
95
            ensure_root(origin)?;
282
89
            LatestAuthor::<T>::remove(para_id);
283
89
            Self::deposit_event(Event::RemovedAuthorData { para_id });
284
89
            Ok(())
285
        }
286
    }
287

            
288
    #[pallet::event]
289
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
290
    pub enum Event<T: Config> {
291
        /// Latest author changed
292
        LatestAuthorChanged {
293
            para_id: ParaId,
294
            block_number: BlockNumber,
295
            new_author: T::AccountId,
296
            latest_slot_number: Slot,
297
        },
298
        /// Removed author data
299
        RemovedAuthorData { para_id: ParaId },
300
    }
301

            
302
    #[pallet::storage]
303
    pub(super) type LatestAuthor<T: Config> =
304
        StorageMap<_, Blake2_128Concat, ParaId, ContainerChainBlockInfo<T::AccountId>, OptionQuery>;
305

            
306
    /// Was the containerAuthorData set?
307
    #[pallet::storage]
308
    pub type DidSetContainerAuthorData<T: Config> = StorageValue<_, bool, ValueQuery>;
309

            
310
    #[pallet::inherent]
311
    impl<T: Config> ProvideInherent for Pallet<T> {
312
        type Call = Call<T>;
313
        type Error = InherentError;
314
        // TODO, what should we put here
315
        const INHERENT_IDENTIFIER: InherentIdentifier =
316
            tp_author_noting_inherent::INHERENT_IDENTIFIER;
317

            
318
        fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
319
            // Return Ok(Some(_)) unconditionally because this inherent is required in every block
320
            Ok(Some(InherentError::Other(Cow::from(
321
                "Pallet Author Noting Inherent required",
322
            ))))
323
        }
324

            
325
16
        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
326
16
            let data = T::RelayOrPara::create_inherent_arg(data);
327

            
328
16
            Some(Call::set_latest_author_data { data })
329
16
        }
330

            
331
        fn is_inherent(call: &Self::Call) -> bool {
332
            matches!(call, Call::set_latest_author_data { .. })
333
        }
334
    }
335
}
336

            
337
impl<T: Config> Pallet<T> {
338
    /// Fetch author and block number from a proof of header
339
178
    fn fetch_block_info_from_proof<S: GenericStorageReader>(
340
178
        relay_state_proof: &S,
341
178
        para_id: ParaId,
342
178
        tanssi_slot: Slot,
343
178
    ) -> Result<ContainerChainBlockInfo<T::AccountId>, Error<T>> {
344
178
        let bytes = para_id.twox_64_concat();
345
        // CONCAT
346
178
        let key = [PARAS_HEADS_INDEX, bytes.as_slice()].concat();
347
        // We might encounter empty vecs
348
        // We only note if we can decode
349
        // In this process several errors can occur, but we will only log if such errors happen
350
        // We first take the HeadData
351
        // If the readError was that the key was not provided (identified by the Proof error),
352
        // then panic
353
178
        let head_data = relay_state_proof
354
178
            .read_entry::<HeadData>(key.as_slice(), None)
355
178
            .map_err(|e| match e {
356
3
                ReadEntryErr::Proof => panic!("Invalid proof provided for para head key"),
357
11
                _ => Error::<T>::FailedReading,
358
11
            })?;
359

            
360
        // We later take the Header decoded
361
167
        let author_header = sp_runtime::generic::Header::<BlockNumber, BlakeTwo256>::decode(
362
167
            &mut head_data.0.as_slice(),
363
        )
364
167
        .map_err(|_| Error::<T>::FailedDecodingHeader)?;
365

            
366
        // Return author from first aura log.
367
        // If there are no aura logs, it iterates over all the logs, then returns the error from the first element.
368
        // This is because it is hard to return a `Vec<Error<T>>`.
369
156
        let mut first_error = None;
370
156
        for aura_digest in author_header.digest().logs() {
371
154
            match Self::author_from_log(aura_digest, para_id, &author_header, tanssi_slot) {
372
154
                Ok(x) => return Ok(x),
373
                Err(e) => {
374
3
                    if first_error.is_none() {
375
3
                        first_error = Some(e);
376
                    }
377
                }
378
            }
379
        }
380

            
381
2
        Err(first_error.unwrap_or(Error::<T>::AuraDigestFirstItem))
382
178
    }
383

            
384
    /// Get block author from aura digest
385
154
    fn author_from_log(
386
154
        aura_digest: &DigestItem,
387
154
        para_id: ParaId,
388
154
        author_header: &sp_runtime::generic::Header<BlockNumber, BlakeTwo256>,
389
154
        tanssi_slot: Slot,
390
154
    ) -> Result<ContainerChainBlockInfo<T::AccountId>, Error<T>> {
391
        // We decode the digest as pre-runtime digest
392
154
        let (id, mut data) = aura_digest
393
154
            .as_pre_runtime()
394
154
            .ok_or(Error::<T>::AsPreRuntimeError)?;
395

            
396
        // Match against the Aura digest
397
154
        if id == AURA_ENGINE_ID {
398
            // DecodeSlot
399
152
            let slot = InherentType::decode(&mut data).map_err(|_| Error::<T>::NonDecodableSlot)?;
400

            
401
            // Fetch Author
402
151
            let author = T::ContainerChainAuthor::author_for_slot(slot, para_id)
403
151
                .ok_or(Error::<T>::AuthorNotFound)?;
404

            
405
151
            Ok(ContainerChainBlockInfo {
406
151
                block_number: author_header.number,
407
151
                author,
408
151
                // We store the slot number of the current tanssi block to have a time-based notion
409
151
                // of when the last block of a container chain was included.
410
151
                // Note that this is not the slot of the container chain block, and it does not
411
151
                // indicate when that block was created, but when it was included in tanssi.
412
151
                latest_slot_number: tanssi_slot,
413
151
            })
414
        } else {
415
2
            Err(Error::<T>::NonAuraDigest)
416
        }
417
154
    }
418

            
419
67
    pub fn latest_author(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
420
67
        LatestAuthor::<T>::get(para_id)
421
67
    }
422
}
423

            
424
#[derive(Encode)]
425
#[cfg_attr(feature = "std", derive(Debug, Decode))]
426
pub enum InherentError {
427
    Other(Cow<'static, str>),
428
}
429

            
430
impl IsFatalError for InherentError {
431
    fn is_fatal_error(&self) -> bool {
432
        match *self {
433
            InherentError::Other(_) => true,
434
        }
435
    }
436
}
437

            
438
impl InherentError {
439
    /// Try to create an instance ouf of the given identifier and data.
440
    #[cfg(feature = "std")]
441
    pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option<Self> {
442
        if id == &INHERENT_IDENTIFIER {
443
            <InherentError as parity_scale_codec::Decode>::decode(&mut &data[..]).ok()
444
        } else {
445
            None
446
        }
447
    }
448
}
449

            
450
impl<T: Config> LatestAuthorInfoFetcher<T::AccountId> for Pallet<T> {
451
12
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
452
12
        LatestAuthor::<T>::get(para_id)
453
12
    }
454
}
455

            
456
/// This pallet has slightly different behavior when used in a parachain vs when used in a relay chain
457
/// (solochain). The main difference is:
458
/// In relay mode, we don't need a storage proof, so the inherent doesn't need any input argument,
459
/// and instead of reading from a storage proof we read from storage directly.
460
pub trait RelayOrPara {
461
    type InherentArg: TypeInfo + Clone + PartialEq + Encode + Decode + core::fmt::Debug;
462
    type GenericStorageReader: GenericStorageReader;
463

            
464
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg;
465
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader;
466

            
467
    #[cfg(feature = "runtime-benchmarks")]
468
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState);
469
}
470

            
471
pub type InherentDataOf<T> = <<T as Config>::RelayOrPara as RelayOrPara>::InherentArg;
472

            
473
pub struct RelayMode;
474
pub struct ParaMode<RCSP: RelaychainStateProvider>(PhantomData<RCSP>);
475

            
476
impl RelayOrPara for RelayMode {
477
    type InherentArg = ();
478
    type GenericStorageReader = NativeStorageReader;
479

            
480
    fn create_inherent_arg(_data: &InherentData) -> Self::InherentArg {
481
        // This ignores the inherent data entirely, so it is compatible with clients that don't add our inherent
482
    }
483

            
484
384
    fn create_storage_reader(_data: Self::InherentArg) -> Self::GenericStorageReader {
485
384
        NativeStorageReader
486
384
    }
487

            
488
    #[cfg(feature = "runtime-benchmarks")]
489
    fn set_current_relay_chain_state(_state: cumulus_pallet_parachain_system::RelayChainState) {}
490
}
491

            
492
impl<RCSP: RelaychainStateProvider> RelayOrPara for ParaMode<RCSP> {
493
    type InherentArg = tp_author_noting_inherent::OwnParachainInherentData;
494
    type GenericStorageReader = GenericStateProof<cumulus_primitives_core::relay_chain::Block>;
495

            
496
16
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg {
497
16
        data.get_data(&INHERENT_IDENTIFIER)
498
16
            .ok()
499
16
            .flatten()
500
16
            .expect("there is not data to be posted; qed")
501
16
    }
502

            
503
122
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader {
504
        let tp_author_noting_inherent::OwnParachainInherentData {
505
122
            relay_storage_proof,
506
122
        } = data;
507

            
508
122
        let relay_chain_state = RCSP::current_relay_chain_state();
509
122
        let relay_storage_root = relay_chain_state.state_root;
510

            
511
122
        GenericStateProof::new(relay_storage_root, relay_storage_proof)
512
122
            .expect("Invalid relay chain state proof")
513
122
    }
514

            
515
    #[cfg(feature = "runtime-benchmarks")]
516
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState) {
517
        RCSP::set_current_relay_chain_state(state)
518
    }
519
}