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
    tp_author_noting_inherent::INHERENT_IDENTIFIER,
48
    tp_traits::{
49
        AuthorNotingHook, ContainerChainBlockInfo, GenericStateProof, GenericStorageReader,
50
        GetContainerChainAuthor, GetCurrentContainerChains, LatestAuthorInfoFetcher,
51
        NativeStorageReader, ReadEntryErr,
52
    },
53
};
54

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

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

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

            
68
pub use pallet::*;
69

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

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

            
79
        type ContainerChains: GetCurrentContainerChains;
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
        /// Weight information for extrinsics in this pallet.
93
        type WeightInfo: WeightInfo;
94
    }
95

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

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

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

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

            
119
20991
            weight += T::DbWeight::get().writes(1);
120
20991

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

            
124
20991
            weight
125
20991
        }
126

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

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

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

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

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

            
160
21001
                let parent_tanssi_slot = u64::from(T::SlotBeacon::slot()).into();
161

            
162
                // TODO: we should probably fetch all authors-containers first
163
                // then pass the vector to the hook, this would allow for a better estimation
164
62442
                for para_id in registered_para_ids {
165
41445
                    match Self::fetch_block_info_from_proof(
166
41445
                        &storage_reader,
167
41445
                        para_id,
168
41445
                        parent_tanssi_slot,
169
41445
                    ) {
170
20232
                        Ok(block_info) => {
171
20232
                            LatestAuthor::<T>::mutate(
172
20232
                                para_id,
173
20232
                                |maybe_old_block_info: &mut Option<
174
                                    ContainerChainBlockInfo<T::AccountId>,
175
20232
                                >| {
176
20232
                                    if let Some(ref mut old_block_info) = maybe_old_block_info {
177
19699
                                        if block_info.block_number > old_block_info.block_number {
178
19693
                                            // We only reward author if the block increases
179
19693
                                            total_weight = total_weight.saturating_add(
180
19693
                                                T::AuthorNotingHook::on_container_author_noted(
181
19693
                                                    &block_info.author,
182
19693
                                                    block_info.block_number,
183
19693
                                                    para_id,
184
19693
                                                ),
185
19693
                                            );
186
19693
                                            let _ = core::mem::replace(old_block_info, block_info);
187
19693
                                        }
188
533
                                    } else {
189
533
                                        // If there is no previous block, we should reward the author of the first block
190
533
                                        total_weight = total_weight.saturating_add(
191
533
                                            T::AuthorNotingHook::on_container_author_noted(
192
533
                                                &block_info.author,
193
533
                                                block_info.block_number,
194
533
                                                para_id,
195
533
                                            ),
196
533
                                        );
197
533
                                        let _ = core::mem::replace(
198
533
                                            maybe_old_block_info,
199
533
                                            Some(block_info),
200
533
                                        );
201
533
                                    }
202
20232
                                },
203
20232
                            );
204
20232
                        }
205
21213
                        Err(e) => log::warn!(
206
21198
                            "Author-noting error {:?} found in para {:?}",
207
21198
                            e,
208
21198
                            u32::from(para_id)
209
                        ),
210
                    }
211
                }
212
14
            }
213

            
214
            // We correctly set the data
215
21011
            DidSetContainerAuthorData::<T>::put(true);
216
21011

            
217
21011
            Ok(PostDispatchInfo {
218
21011
                actual_weight: Some(total_weight),
219
21011
                pays_fee: Pays::No,
220
21011
            })
221
        }
222

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

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

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

            
274
20386
    #[pallet::storage]
275
    pub(super) type LatestAuthor<T: Config> =
276
        StorageMap<_, Blake2_128Concat, ParaId, ContainerChainBlockInfo<T::AccountId>, OptionQuery>;
277

            
278
    /// Was the containerAuthorData set?
279
168012
    #[pallet::storage]
280
    pub(super) type DidSetContainerAuthorData<T: Config> = StorageValue<_, bool, ValueQuery>;
281

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

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

            
297
20992
        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
298
20992
            let data = T::RelayOrPara::create_inherent_arg(data);
299
20992

            
300
20992
            Some(Call::set_latest_author_data { data })
301
20992
        }
302

            
303
20976
        fn is_inherent(call: &Self::Call) -> bool {
304
20976
            matches!(call, Call::set_latest_author_data { .. })
305
20976
        }
306
    }
307
}
308

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

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

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

            
353
21200
        Err(first_error.unwrap_or(Error::<T>::AuraDigestFirstItem))
354
41441
    }
355

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

            
368
        // Match against the Aura digest
369
41433
        if id == AURA_ENGINE_ID {
370
            // DecodeSlot
371
41431
            let slot = InherentType::decode(&mut data).map_err(|_| Error::<T>::NonDecodableSlot)?;
372

            
373
            // Fetch Author
374
41430
            let author = T::ContainerChainAuthor::author_for_slot(slot, para_id)
375
41430
                .ok_or(Error::<T>::AuthorNotFound)?;
376

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

            
391
36
    pub fn latest_author(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
392
36
        LatestAuthor::<T>::get(para_id)
393
36
    }
394
}
395

            
396
#[derive(Encode)]
397
#[cfg_attr(feature = "std", derive(Debug, Decode))]
398
pub enum InherentError {
399
    Other(RuntimeString),
400
}
401

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

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

            
422
impl<T: Config> LatestAuthorInfoFetcher<T::AccountId> for Pallet<T> {
423
24
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
424
24
        LatestAuthor::<T>::get(para_id)
425
24
    }
426
}
427

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

            
436
    fn create_inherent_arg(data: &InherentData) -> Self::InherentArg;
437
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader;
438

            
439
    #[cfg(feature = "runtime-benchmarks")]
440
    fn set_current_relay_chain_state(state: cumulus_pallet_parachain_system::RelayChainState);
441
}
442

            
443
pub type InherentDataOf<T> = <<T as Config>::RelayOrPara as RelayOrPara>::InherentArg;
444

            
445
pub struct RelayMode;
446
pub struct ParaMode<RCSP: RelaychainStateProvider>(PhantomData<RCSP>);
447

            
448
impl RelayOrPara for RelayMode {
449
    type InherentArg = ();
450
    type GenericStorageReader = NativeStorageReader;
451

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

            
456
56
    fn create_storage_reader(_data: Self::InherentArg) -> Self::GenericStorageReader {
457
56
        NativeStorageReader
458
56
    }
459

            
460
    #[cfg(feature = "runtime-benchmarks")]
461
    fn set_current_relay_chain_state(_state: cumulus_pallet_parachain_system::RelayChainState) {}
462
}
463

            
464
impl<RCSP: RelaychainStateProvider> RelayOrPara for ParaMode<RCSP> {
465
    type InherentArg = tp_author_noting_inherent::OwnParachainInherentData;
466
    type GenericStorageReader = GenericStateProof<cumulus_primitives_core::relay_chain::Block>;
467

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

            
475
20993
    fn create_storage_reader(data: Self::InherentArg) -> Self::GenericStorageReader {
476
20993
        let tp_author_noting_inherent::OwnParachainInherentData {
477
20993
            relay_storage_proof,
478
20993
        } = data;
479
20993

            
480
20993
        let relay_chain_state = RCSP::current_relay_chain_state();
481
20993
        let relay_storage_root = relay_chain_state.state_root;
482
20993

            
483
20993
        GenericStateProof::new(relay_storage_root, relay_storage_proof)
484
20993
            .expect("Invalid relay chain state proof")
485
20993
    }
486

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