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
14352
#[frame_support::pallet]
74
pub mod pallet {
75
    use super::*;
76

            
77
    #[pallet::config]
78
    pub trait Config: frame_system::Config {
79
        /// The overarching event type.
80
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
81

            
82
        type ContainerChains: GetContainerChainsWithCollators<Self::AccountId>;
83

            
84
        type SlotBeacon: SlotBeacon;
85

            
86
        type ContainerChainAuthor: GetContainerChainAuthor<Self::AccountId>;
87

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

            
93
        type RelayOrPara: RelayOrPara;
94

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

            
99
        /// Weight information for extrinsics in this pallet.
100
        type WeightInfo: WeightInfo;
101
    }
102

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

            
115
2816
    #[pallet::pallet]
116
    pub struct Pallet<T>(PhantomData<T>);
117

            
118
66623
    #[pallet::hooks]
119
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
120
23823
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
121
23823
            let mut weight = Weight::zero();
122
23823

            
123
23823
            // We clear this storage item to make sure its always included
124
23823
            DidSetContainerAuthorData::<T>::kill();
125
23823

            
126
23823
            weight.saturating_accrue(T::DbWeight::get().writes(1));
127
23823

            
128
23823
            // The read onfinalizes
129
23823
            weight.saturating_accrue(T::DbWeight::get().reads(1));
130
23823

            
131
23823
            weight
132
23823
        }
133

            
134
23819
        fn on_finalize(_: BlockNumberFor<T>) {
135
23819
            assert!(
136
23819
                <DidSetContainerAuthorData<T>>::exists(),
137
1
                "Container chain author data needs to be present in every block!"
138
            );
139
23818
        }
140
    }
141

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

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

            
158
23722
            let container_chains_to_check: Vec<_> =
159
23722
                T::ContainerChains::container_chains_with_collators(ForSession::Current)
160
23722
                    .into_iter()
161
44453
                    .filter_map(|(para_id, collators)| (!collators.is_empty()).then_some(para_id))
162
23722
                    .collect();
163
23722
            let mut total_weight =
164
23722
                T::WeightInfo::set_latest_author_data(container_chains_to_check.len() as u32);
165
23722

            
166
23722
            // We do this first to make sure we don't do 2 reads (parachains and relay state)
167
23722
            // when we have no containers registered
168
23722
            // Essentially one can pass an empty proof if no container-chains are registered
169
23722
            if !container_chains_to_check.is_empty() {
170
22688
                let storage_reader = T::RelayOrPara::create_storage_reader(data);
171
22688

            
172
22688
                let parent_tanssi_slot = u64::from(T::SlotBeacon::slot()).into();
173
22688
                let mut infos = Vec::with_capacity(container_chains_to_check.len());
174

            
175
46431
                for para_id in container_chains_to_check {
176
23747
                    match Self::fetch_block_info_from_proof(
177
23747
                        &storage_reader,
178
23747
                        para_id,
179
23747
                        parent_tanssi_slot,
180
23747
                    ) {
181
23308
                        Ok(block_info) => {
182
23308
                            LatestAuthor::<T>::mutate(
183
23308
                                para_id,
184
23308
                                |maybe_old_block_info: &mut Option<
185
                                    ContainerChainBlockInfo<T::AccountId>,
186
23308
                                >| {
187
23308
                                    // No block number is the same as the last block number being 0:
188
23308
                                    // the first block created by collators is block number 1.
189
23308
                                    let old_block_number = maybe_old_block_info
190
23308
                                        .as_ref()
191
23308
                                        .map(|old_block_info| old_block_info.block_number)
192
23308
                                        .unwrap_or(0);
193
23308
                                    // We only reward author if the block increases
194
23308
                                    // If there is no previous block, we should reward the author of the first block
195
23308
                                    if block_info.block_number > old_block_number {
196
22804
                                        let bi = block_info.clone();
197
22804
                                        let info = AuthorNotingInfo {
198
22804
                                            author: block_info.author,
199
22804
                                            block_number: block_info.block_number,
200
22804
                                            para_id,
201
22804
                                        };
202
22804
                                        infos.push(info);
203
22804
                                        *maybe_old_block_info = Some(bi);
204
22804
                                    }
205
23308
                                },
206
23308
                            );
207
23308
                        }
208
439
                        Err(e) => log::warn!(
209
426
                            "Author-noting error {:?} found in para {:?}",
210
426
                            e,
211
426
                            u32::from(para_id)
212
                        ),
213
                    }
214
                }
215

            
216
22684
                total_weight
217
22684
                    .saturating_accrue(T::AuthorNotingHook::on_container_authors_noted(&infos));
218
1034
            }
219

            
220
            // We correctly set the data
221
23718
            DidSetContainerAuthorData::<T>::put(true);
222
23718

            
223
23718
            Ok(PostDispatchInfo {
224
23718
                actual_weight: Some(total_weight),
225
23718
                pays_fee: Pays::No,
226
23718
            })
227
        }
228

            
229
        #[pallet::call_index(1)]
230
        #[pallet::weight(T::WeightInfo::set_author())]
231
        pub fn set_author(
232
            origin: OriginFor<T>,
233
            para_id: ParaId,
234
            block_number: BlockNumber,
235
            author: T::AccountId,
236
            latest_slot_number: Slot,
237
27
        ) -> DispatchResult {
238
27
            ensure_root(origin)?;
239
23
            LatestAuthor::<T>::insert(
240
23
                para_id,
241
23
                ContainerChainBlockInfo {
242
23
                    block_number,
243
23
                    author: author.clone(),
244
23
                    latest_slot_number,
245
23
                },
246
23
            );
247
23
            Self::deposit_event(Event::LatestAuthorChanged {
248
23
                para_id,
249
23
                block_number,
250
23
                new_author: author,
251
23
                latest_slot_number,
252
23
            });
253
23
            Ok(())
254
        }
255

            
256
        #[pallet::call_index(2)]
257
        #[pallet::weight(T::WeightInfo::kill_author_data())]
258
103
        pub fn kill_author_data(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
259
103
            ensure_root(origin)?;
260
99
            LatestAuthor::<T>::remove(para_id);
261
99
            Self::deposit_event(Event::RemovedAuthorData { para_id });
262
99
            Ok(())
263
        }
264
    }
265

            
266
624
    #[pallet::event]
267
122
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
268
    pub enum Event<T: Config> {
269
        /// Latest author changed
270
        LatestAuthorChanged {
271
            para_id: ParaId,
272
            block_number: BlockNumber,
273
            new_author: T::AccountId,
274
            latest_slot_number: Slot,
275
        },
276
        /// Removed author data
277
        RemovedAuthorData { para_id: ParaId },
278
    }
279

            
280
24121
    #[pallet::storage]
281
    pub(super) type LatestAuthor<T: Config> =
282
        StorageMap<_, Blake2_128Concat, ParaId, ContainerChainBlockInfo<T::AccountId>, OptionQuery>;
283

            
284
    /// Was the containerAuthorData set?
285
191068
    #[pallet::storage]
286
    pub type DidSetContainerAuthorData<T: Config> = StorageValue<_, bool, ValueQuery>;
287

            
288
    #[pallet::inherent]
289
    impl<T: Config> ProvideInherent for Pallet<T> {
290
        type Call = Call<T>;
291
        type Error = InherentError;
292
        // TODO, what should we put here
293
        const INHERENT_IDENTIFIER: InherentIdentifier =
294
            tp_author_noting_inherent::INHERENT_IDENTIFIER;
295

            
296
        fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
297
            // Return Ok(Some(_)) unconditionally because this inherent is required in every block
298
            Ok(Some(InherentError::Other(Cow::from(
299
                "Pallet Author Noting Inherent required",
300
            ))))
301
        }
302

            
303
23686
        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
304
23686
            let data = T::RelayOrPara::create_inherent_arg(data);
305
23686

            
306
23686
            Some(Call::set_latest_author_data { data })
307
23686
        }
308

            
309
23670
        fn is_inherent(call: &Self::Call) -> bool {
310
23670
            matches!(call, Call::set_latest_author_data { .. })
311
23670
        }
312
    }
313
}
314

            
315
impl<T: Config> Pallet<T> {
316
    /// Fetch author and block number from a proof of header
317
23746
    fn fetch_block_info_from_proof<S: GenericStorageReader>(
318
23746
        relay_state_proof: &S,
319
23746
        para_id: ParaId,
320
23746
        tanssi_slot: Slot,
321
23746
    ) -> Result<ContainerChainBlockInfo<T::AccountId>, Error<T>> {
322
23746
        let bytes = para_id.twox_64_concat();
323
23746
        // CONCAT
324
23746
        let key = [PARAS_HEADS_INDEX, bytes.as_slice()].concat();
325
        // We might encounter empty vecs
326
        // We only note if we can decode
327
        // In this process several errors can occur, but we will only log if such errors happen
328
        // We first take the HeadData
329
        // If the readError was that the key was not provided (identified by the Proof error),
330
        // then panic
331
23746
        let head_data = relay_state_proof
332
23746
            .read_entry::<HeadData>(key.as_slice(), None)
333
23746
            .map_err(|e| match e {
334
3
                ReadEntryErr::Proof => panic!("Invalid proof provided for para head key"),
335
428
                _ => Error::<T>::FailedReading,
336
23746
            })?;
337

            
338
        // We later take the Header decoded
339
23318
        let author_header = sp_runtime::generic::Header::<BlockNumber, BlakeTwo256>::decode(
340
23318
            &mut head_data.0.as_slice(),
341
23318
        )
342
23318
        .map_err(|_| Error::<T>::FailedDecodingHeader)?;
343

            
344
        // Return author from first aura log.
345
        // If there are no aura logs, it iterates over all the logs, then returns the error from the first element.
346
        // This is because it is hard to return a `Vec<Error<T>>`.
347
23313
        let mut first_error = None;
348
23313
        for aura_digest in author_header.digest().logs() {
349
23311
            match Self::author_from_log(aura_digest, para_id, &author_header, tanssi_slot) {
350
23308
                Ok(x) => return Ok(x),
351
3
                Err(e) => {
352
3
                    if first_error.is_none() {
353
3
                        first_error = Some(e);
354
3
                    }
355
                }
356
            }
357
        }
358

            
359
2
        Err(first_error.unwrap_or(Error::<T>::AuraDigestFirstItem))
360
23743
    }
361

            
362
    /// Get block author from aura digest
363
23311
    fn author_from_log(
364
23311
        aura_digest: &DigestItem,
365
23311
        para_id: ParaId,
366
23311
        author_header: &sp_runtime::generic::Header<BlockNumber, BlakeTwo256>,
367
23311
        tanssi_slot: Slot,
368
23311
    ) -> Result<ContainerChainBlockInfo<T::AccountId>, Error<T>> {
369
        // We decode the digest as pre-runtime digest
370
23311
        let (id, mut data) = aura_digest
371
23311
            .as_pre_runtime()
372
23311
            .ok_or(Error::<T>::AsPreRuntimeError)?;
373

            
374
        // Match against the Aura digest
375
23311
        if id == AURA_ENGINE_ID {
376
            // DecodeSlot
377
23309
            let slot = InherentType::decode(&mut data).map_err(|_| Error::<T>::NonDecodableSlot)?;
378

            
379
            // Fetch Author
380
23308
            let author = T::ContainerChainAuthor::author_for_slot(slot, para_id)
381
23308
                .ok_or(Error::<T>::AuthorNotFound)?;
382

            
383
23308
            Ok(ContainerChainBlockInfo {
384
23308
                block_number: author_header.number,
385
23308
                author,
386
23308
                // We store the slot number of the current tanssi block to have a time-based notion
387
23308
                // of when the last block of a container chain was included.
388
23308
                // Note that this is not the slot of the container chain block, and it does not
389
23308
                // indicate when that block was created, but when it was included in tanssi.
390
23308
                latest_slot_number: tanssi_slot,
391
23308
            })
392
        } else {
393
2
            Err(Error::<T>::NonAuraDigest)
394
        }
395
23311
    }
396

            
397
43
    pub fn latest_author(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
398
43
        LatestAuthor::<T>::get(para_id)
399
43
    }
400
}
401

            
402
#[derive(Encode)]
403
#[cfg_attr(feature = "std", derive(Debug, Decode))]
404
pub enum InherentError {
405
    Other(Cow<'static, str>),
406
}
407

            
408
impl IsFatalError for InherentError {
409
    fn is_fatal_error(&self) -> bool {
410
        match *self {
411
            InherentError::Other(_) => true,
412
        }
413
    }
414
}
415

            
416
impl InherentError {
417
    /// Try to create an instance ouf of the given identifier and data.
418
    #[cfg(feature = "std")]
419
    pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option<Self> {
420
        if id == &INHERENT_IDENTIFIER {
421
            <InherentError as parity_scale_codec::Decode>::decode(&mut &data[..]).ok()
422
        } else {
423
            None
424
        }
425
    }
426
}
427

            
428
impl<T: Config> LatestAuthorInfoFetcher<T::AccountId> for Pallet<T> {
429
24
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
430
24
        LatestAuthor::<T>::get(para_id)
431
24
    }
432
}
433

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

            
442
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg;
443
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader;
444

            
445
    #[cfg(feature = "runtime-benchmarks")]
446
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState);
447
}
448

            
449
pub type InherentDataOf<T> = <<T as Config>::RelayOrPara as RelayOrPara>::InherentArg;
450

            
451
pub struct RelayMode;
452
pub struct ParaMode<RCSP: RelaychainStateProvider>(PhantomData<RCSP>);
453

            
454
impl RelayOrPara for RelayMode {
455
    type InherentArg = ();
456
    type GenericStorageReader = NativeStorageReader;
457

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

            
462
288
    fn create_storage_reader(_data: Self::InherentArg) -> Self::GenericStorageReader {
463
288
        NativeStorageReader
464
288
    }
465

            
466
    #[cfg(feature = "runtime-benchmarks")]
467
    fn set_current_relay_chain_state(_state: cumulus_pallet_parachain_system::RelayChainState) {}
468
}
469

            
470
impl<RCSP: RelaychainStateProvider> RelayOrPara for ParaMode<RCSP> {
471
    type InherentArg = tp_author_noting_inherent::OwnParachainInherentData;
472
    type GenericStorageReader = GenericStateProof<cumulus_primitives_core::relay_chain::Block>;
473

            
474
23686
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg {
475
23686
        data.get_data(&INHERENT_IDENTIFIER)
476
23686
            .ok()
477
23686
            .flatten()
478
23686
            .expect("there is not data to be posted; qed")
479
23686
    }
480

            
481
22670
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader {
482
22670
        let tp_author_noting_inherent::OwnParachainInherentData {
483
22670
            relay_storage_proof,
484
22670
        } = data;
485
22670

            
486
22670
        let relay_chain_state = RCSP::current_relay_chain_state();
487
22670
        let relay_storage_root = relay_chain_state.state_root;
488
22670

            
489
22670
        GenericStateProof::new(relay_storage_root, relay_storage_proof)
490
22670
            .expect("Invalid relay chain state proof")
491
22670
    }
492

            
493
    #[cfg(feature = "runtime-benchmarks")]
494
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState) {
495
        RCSP::set_current_relay_chain_state(state)
496
    }
497
}