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()), DispatchClass::Mandatory))]
143
        #[allow(clippy::useless_conversion)]
144
        pub fn set_latest_author_data(
145
            origin: OriginFor<T>,
146
            data: InherentDataOf<T>,
147
152
        ) -> DispatchResultWithPostInfo {
148
152
            ensure_none(origin)?;
149

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

            
155
151
            let container_chains_to_check: Vec<_> =
156
151
                T::ContainerChains::container_chains_with_collators(ForSession::Current)
157
151
                    .into_iter()
158
191
                    .filter_map(|(para_id, collators)| (!collators.is_empty()).then_some(para_id))
159
151
                    .collect();
160
151
            let mut total_weight =
161
151
                T::WeightInfo::set_latest_author_data(container_chains_to_check.len() as u32);
162

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

            
169
140
                let parent_tanssi_slot = u64::from(T::SlotBeacon::slot()).into();
170
140
                let mut infos = Vec::with_capacity(container_chains_to_check.len());
171

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

            
213
140
                total_weight
214
140
                    .saturating_accrue(T::AuthorNotingHook::on_container_authors_noted(&infos));
215
11
            }
216

            
217
            // We correctly set the data
218
151
            DidSetContainerAuthorData::<T>::put(true);
219

            
220
151
            Ok(PostDispatchInfo {
221
151
                actual_weight: Some(total_weight),
222
151
                pays_fee: Pays::No,
223
151
            })
224
        }
225

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

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

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

            
277
    #[pallet::storage]
278
    pub(super) type LatestAuthor<T: Config> =
279
        StorageMap<_, Blake2_128Concat, ParaId, ContainerChainBlockInfo<T::AccountId>, OptionQuery>;
280

            
281
    /// Was the containerAuthorData set?
282
    #[pallet::storage]
283
    pub type DidSetContainerAuthorData<T: Config> = StorageValue<_, bool, ValueQuery>;
284

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

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

            
300
16
        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
301
16
            let data = T::RelayOrPara::create_inherent_arg(data);
302

            
303
16
            Some(Call::set_latest_author_data { data })
304
16
        }
305

            
306
        fn is_inherent(call: &Self::Call) -> bool {
307
            matches!(call, Call::set_latest_author_data { .. })
308
        }
309
    }
310
}
311

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

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

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

            
356
2
        Err(first_error.unwrap_or(Error::<T>::AuraDigestFirstItem))
357
166
    }
358

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

            
371
        // Match against the Aura digest
372
148
        if id == AURA_ENGINE_ID {
373
            // DecodeSlot
374
146
            let slot = InherentType::decode(&mut data).map_err(|_| Error::<T>::NonDecodableSlot)?;
375

            
376
            // Fetch Author
377
145
            let author = T::ContainerChainAuthor::author_for_slot(slot, para_id)
378
145
                .ok_or(Error::<T>::AuthorNotFound)?;
379

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

            
394
61
    pub fn latest_author(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
395
61
        LatestAuthor::<T>::get(para_id)
396
61
    }
397
}
398

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

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

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

            
425
impl<T: Config> LatestAuthorInfoFetcher<T::AccountId> for Pallet<T> {
426
12
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
427
12
        LatestAuthor::<T>::get(para_id)
428
12
    }
429
}
430

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

            
439
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg;
440
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader;
441

            
442
    #[cfg(feature = "runtime-benchmarks")]
443
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState);
444
}
445

            
446
pub type InherentDataOf<T> = <<T as Config>::RelayOrPara as RelayOrPara>::InherentArg;
447

            
448
pub struct RelayMode;
449
pub struct ParaMode<RCSP: RelaychainStateProvider>(PhantomData<RCSP>);
450

            
451
impl RelayOrPara for RelayMode {
452
    type InherentArg = ();
453
    type GenericStorageReader = NativeStorageReader;
454

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

            
459
288
    fn create_storage_reader(_data: Self::InherentArg) -> Self::GenericStorageReader {
460
288
        NativeStorageReader
461
288
    }
462

            
463
    #[cfg(feature = "runtime-benchmarks")]
464
    fn set_current_relay_chain_state(_state: cumulus_pallet_parachain_system::RelayChainState) {}
465
}
466

            
467
impl<RCSP: RelaychainStateProvider> RelayOrPara for ParaMode<RCSP> {
468
    type InherentArg = tp_author_noting_inherent::OwnParachainInherentData;
469
    type GenericStorageReader = GenericStateProof<cumulus_primitives_core::relay_chain::Block>;
470

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

            
478
122
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader {
479
        let tp_author_noting_inherent::OwnParachainInherentData {
480
122
            relay_storage_proof,
481
122
        } = data;
482

            
483
122
        let relay_chain_state = RCSP::current_relay_chain_state();
484
122
        let relay_storage_root = relay_chain_state.state_root;
485

            
486
122
        GenericStateProof::new(relay_storage_root, relay_storage_proof)
487
122
            .expect("Invalid relay chain state proof")
488
122
    }
489

            
490
    #[cfg(feature = "runtime-benchmarks")]
491
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState) {
492
        RCSP::set_current_relay_chain_state(state)
493
    }
494
}