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

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

            
56
#[cfg(test)]
57
mod mock;
58

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

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

            
69
pub use pallet::*;
70

            
71
26733
#[frame_support::pallet]
72
pub mod pallet {
73
    use super::*;
74

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

            
80
        type ContainerChains: GetCurrentContainerChains;
81

            
82
        type SlotBeacon: SlotBeacon;
83

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

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

            
91
        type RelayOrPara: RelayOrPara;
92

            
93
        /// Weight information for extrinsics in this pallet.
94
        type WeightInfo: WeightInfo;
95
    }
96

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

            
109
602
    #[pallet::pallet]
110
    pub struct Pallet<T>(PhantomData<T>);
111

            
112
43674
    #[pallet::hooks]
113
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
114
21837
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
115
21837
            let mut weight = Weight::zero();
116
21837

            
117
21837
            // We clear this storage item to make sure its always included
118
21837
            DidSetContainerAuthorData::<T>::kill();
119
21837

            
120
21837
            weight += T::DbWeight::get().writes(1);
121
21837

            
122
21837
            // The read onfinalizes
123
21837
            weight += T::DbWeight::get().reads(1);
124
21837

            
125
21837
            weight
126
21837
        }
127

            
128
21833
        fn on_finalize(_: BlockNumberFor<T>) {
129
21833
            assert!(
130
21833
                <DidSetContainerAuthorData<T>>::exists(),
131
1
                "Container chain author data needs to be present in every block!"
132
            );
133
21832
        }
134
    }
135

            
136
113539
    #[pallet::call]
137
    impl<T: Config> Pallet<T> {
138
        #[pallet::call_index(0)]
139
        #[pallet::weight((T::WeightInfo::set_latest_author_data(<T::ContainerChains as GetCurrentContainerChains>::MaxContainerChains::get()), DispatchClass::Mandatory))]
140
        pub fn set_latest_author_data(
141
            origin: OriginFor<T>,
142
            data: InherentDataOf<T>,
143
21862
        ) -> DispatchResultWithPostInfo {
144
21862
            ensure_none(origin)?;
145

            
146
21862
            assert!(
147
21862
                !<DidSetContainerAuthorData<T>>::exists(),
148
1
                "DidSetContainerAuthorData must be updated only once in a block",
149
            );
150

            
151
21861
            let registered_para_ids = T::ContainerChains::current_container_chains();
152
21861
            let mut total_weight =
153
21861
                T::WeightInfo::set_latest_author_data(registered_para_ids.len() as u32);
154
21861

            
155
21861
            // We do this first to make sure we don't do 2 reads (parachains and relay state)
156
21861
            // when we have no containers registered
157
21861
            // Essentially one can pass an empty proof if no container-chains are registered
158
21861
            if !registered_para_ids.is_empty() {
159
21847
                let storage_reader = T::RelayOrPara::create_storage_reader(data);
160
21847

            
161
21847
                let parent_tanssi_slot = u64::from(T::SlotBeacon::slot()).into();
162
21847
                let mut infos = Vec::with_capacity(registered_para_ids.len());
163

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

            
205
21843
                total_weight = total_weight
206
21843
                    .saturating_add(T::AuthorNotingHook::on_container_authors_noted(&infos));
207
14
            }
208

            
209
            // We correctly set the data
210
21857
            DidSetContainerAuthorData::<T>::put(true);
211
21857

            
212
21857
            Ok(PostDispatchInfo {
213
21857
                actual_weight: Some(total_weight),
214
21857
                pays_fee: Pays::No,
215
21857
            })
216
        }
217

            
218
        #[pallet::call_index(1)]
219
        #[pallet::weight(T::WeightInfo::set_author())]
220
        pub fn set_author(
221
            origin: OriginFor<T>,
222
            para_id: ParaId,
223
            block_number: BlockNumber,
224
            author: T::AccountId,
225
            latest_slot_number: Slot,
226
25
        ) -> DispatchResult {
227
25
            ensure_root(origin)?;
228
22
            LatestAuthor::<T>::insert(
229
22
                para_id,
230
22
                ContainerChainBlockInfo {
231
22
                    block_number,
232
22
                    author: author.clone(),
233
22
                    latest_slot_number,
234
22
                },
235
22
            );
236
22
            Self::deposit_event(Event::LatestAuthorChanged {
237
22
                para_id,
238
22
                block_number,
239
22
                new_author: author,
240
22
                latest_slot_number,
241
22
            });
242
22
            Ok(())
243
        }
244

            
245
        #[pallet::call_index(2)]
246
        #[pallet::weight(T::WeightInfo::kill_author_data())]
247
93
        pub fn kill_author_data(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
248
93
            ensure_root(origin)?;
249
90
            LatestAuthor::<T>::remove(para_id);
250
90
            Self::deposit_event(Event::RemovedAuthorData { para_id });
251
90
            Ok(())
252
        }
253
    }
254

            
255
    #[pallet::event]
256
112
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
257
    pub enum Event<T: Config> {
258
1
        /// Latest author changed
259
        LatestAuthorChanged {
260
            para_id: ParaId,
261
            block_number: BlockNumber,
262
            new_author: T::AccountId,
263
            latest_slot_number: Slot,
264
        },
265
1
        /// Removed author data
266
        RemovedAuthorData { para_id: ParaId },
267
    }
268

            
269
21616
    #[pallet::storage]
270
    pub(super) type LatestAuthor<T: Config> =
271
        StorageMap<_, Blake2_128Concat, ParaId, ContainerChainBlockInfo<T::AccountId>, OptionQuery>;
272

            
273
    /// Was the containerAuthorData set?
274
174780
    #[pallet::storage]
275
    pub(super) type DidSetContainerAuthorData<T: Config> = StorageValue<_, bool, ValueQuery>;
276

            
277
    #[pallet::inherent]
278
    impl<T: Config> ProvideInherent for Pallet<T> {
279
        type Call = Call<T>;
280
        type Error = InherentError;
281
        // TODO, what should we put here
282
        const INHERENT_IDENTIFIER: InherentIdentifier =
283
            tp_author_noting_inherent::INHERENT_IDENTIFIER;
284

            
285
        fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
286
            // Return Ok(Some(_)) unconditionally because this inherent is required in every block
287
            Ok(Some(InherentError::Other(
288
                sp_runtime::RuntimeString::Borrowed("Pallet Author Noting Inherent required"),
289
            )))
290
        }
291

            
292
21838
        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
293
21838
            let data = T::RelayOrPara::create_inherent_arg(data);
294
21838

            
295
21838
            Some(Call::set_latest_author_data { data })
296
21838
        }
297

            
298
21822
        fn is_inherent(call: &Self::Call) -> bool {
299
21822
            matches!(call, Call::set_latest_author_data { .. })
300
21822
        }
301
    }
302
}
303

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

            
327
        // We later take the Header decoded
328
43005
        let author_header = sp_runtime::generic::Header::<BlockNumber, BlakeTwo256>::decode(
329
43005
            &mut head_data.0.as_slice(),
330
43005
        )
331
43005
        .map_err(|_| Error::<T>::FailedDecodingHeader)?;
332

            
333
        // Return author from first aura log.
334
        // If there are no aura logs, it iterates over all the logs, then returns the error from the first element.
335
        // This is because it is hard to return a `Vec<Error<T>>`.
336
43001
        let mut first_error = None;
337
43001
        for aura_digest in author_header.digest().logs() {
338
42999
            match Self::author_from_log(aura_digest, para_id, &author_header, tanssi_slot) {
339
21444
                Ok(x) => return Ok(x),
340
21555
                Err(e) => {
341
21555
                    if first_error.is_none() {
342
21555
                        first_error = Some(e);
343
21555
                    }
344
                }
345
            }
346
        }
347

            
348
21554
        Err(first_error.unwrap_or(Error::<T>::AuraDigestFirstItem))
349
43007
    }
350

            
351
    /// Get block author from aura digest
352
42999
    fn author_from_log(
353
42999
        aura_digest: &DigestItem,
354
42999
        para_id: ParaId,
355
42999
        author_header: &sp_runtime::generic::Header<BlockNumber, BlakeTwo256>,
356
42999
        tanssi_slot: Slot,
357
42999
    ) -> Result<ContainerChainBlockInfo<T::AccountId>, Error<T>> {
358
        // We decode the digest as pre-runtime digest
359
42999
        let (id, mut data) = aura_digest
360
42999
            .as_pre_runtime()
361
42999
            .ok_or(Error::<T>::AsPreRuntimeError)?;
362

            
363
        // Match against the Aura digest
364
42999
        if id == AURA_ENGINE_ID {
365
            // DecodeSlot
366
42997
            let slot = InherentType::decode(&mut data).map_err(|_| Error::<T>::NonDecodableSlot)?;
367

            
368
            // Fetch Author
369
42996
            let author = T::ContainerChainAuthor::author_for_slot(slot, para_id)
370
42996
                .ok_or(Error::<T>::AuthorNotFound)?;
371

            
372
21444
            Ok(ContainerChainBlockInfo {
373
21444
                block_number: author_header.number,
374
21444
                author,
375
21444
                // We store the slot number of the current tanssi block to have a time-based notion
376
21444
                // of when the last block of a container chain was included.
377
21444
                // Note that this is not the slot of the container chain block, and it does not
378
21444
                // indicate when that block was created, but when it was included in tanssi.
379
21444
                latest_slot_number: tanssi_slot,
380
21444
            })
381
        } else {
382
2
            Err(Error::<T>::NonAuraDigest)
383
        }
384
42999
    }
385

            
386
36
    pub fn latest_author(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
387
36
        LatestAuthor::<T>::get(para_id)
388
36
    }
389
}
390

            
391
#[derive(Encode)]
392
#[cfg_attr(feature = "std", derive(Debug, Decode))]
393
pub enum InherentError {
394
    Other(RuntimeString),
395
}
396

            
397
impl IsFatalError for InherentError {
398
    fn is_fatal_error(&self) -> bool {
399
        match *self {
400
            InherentError::Other(_) => true,
401
        }
402
    }
403
}
404

            
405
impl InherentError {
406
    /// Try to create an instance ouf of the given identifier and data.
407
    #[cfg(feature = "std")]
408
    pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option<Self> {
409
        if id == &INHERENT_IDENTIFIER {
410
            <InherentError as parity_scale_codec::Decode>::decode(&mut &data[..]).ok()
411
        } else {
412
            None
413
        }
414
    }
415
}
416

            
417
impl<T: Config> LatestAuthorInfoFetcher<T::AccountId> for Pallet<T> {
418
24
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
419
24
        LatestAuthor::<T>::get(para_id)
420
24
    }
421
}
422

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

            
431
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg;
432
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader;
433

            
434
    #[cfg(feature = "runtime-benchmarks")]
435
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState);
436
}
437

            
438
pub type InherentDataOf<T> = <<T as Config>::RelayOrPara as RelayOrPara>::InherentArg;
439

            
440
pub struct RelayMode;
441
pub struct ParaMode<RCSP: RelaychainStateProvider>(PhantomData<RCSP>);
442

            
443
impl RelayOrPara for RelayMode {
444
    type InherentArg = ();
445
    type GenericStorageReader = NativeStorageReader;
446

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

            
451
56
    fn create_storage_reader(_data: Self::InherentArg) -> Self::GenericStorageReader {
452
56
        NativeStorageReader
453
56
    }
454

            
455
    #[cfg(feature = "runtime-benchmarks")]
456
    fn set_current_relay_chain_state(_state: cumulus_pallet_parachain_system::RelayChainState) {}
457
}
458

            
459
impl<RCSP: RelaychainStateProvider> RelayOrPara for ParaMode<RCSP> {
460
    type InherentArg = tp_author_noting_inherent::OwnParachainInherentData;
461
    type GenericStorageReader = GenericStateProof<cumulus_primitives_core::relay_chain::Block>;
462

            
463
21838
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg {
464
21838
        data.get_data(&INHERENT_IDENTIFIER)
465
21838
            .ok()
466
21838
            .flatten()
467
21838
            .expect("there is not data to be posted; qed")
468
21838
    }
469

            
470
21839
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader {
471
21839
        let tp_author_noting_inherent::OwnParachainInherentData {
472
21839
            relay_storage_proof,
473
21839
        } = data;
474
21839

            
475
21839
        let relay_chain_state = RCSP::current_relay_chain_state();
476
21839
        let relay_storage_root = relay_chain_state.state_root;
477
21839

            
478
21839
        GenericStateProof::new(relay_storage_root, relay_storage_proof)
479
21839
            .expect("Invalid relay chain state proof")
480
21839
    }
481

            
482
    #[cfg(feature = "runtime-benchmarks")]
483
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState) {
484
        RCSP::set_current_relay_chain_state(state)
485
    }
486
}