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},
47
    sp_std::borrow::Cow,
48
    sp_std::vec::Vec,
49
    tp_author_noting_inherent::INHERENT_IDENTIFIER,
50
    tp_traits::{
51
        AuthorNotingHook, AuthorNotingInfo, ContainerChainBlockInfo, ForSession, GenericStateProof,
52
        GenericStorageReader, GetContainerChainAuthor, GetContainerChainsWithCollators,
53
        LatestAuthorInfoFetcher, NativeStorageReader, ReadEntryErr,
54
    },
55
};
56

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

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

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

            
70
pub use pallet::*;
71

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

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

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

            
83
        type SlotBeacon: SlotBeacon;
84

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

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

            
92
        type RelayOrPara: RelayOrPara;
93

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

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

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

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

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

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

            
125
23583
            weight.saturating_accrue(T::DbWeight::get().writes(1));
126
23583

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

            
130
23583
            weight
131
23583
        }
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
305
23446
            Some(Call::set_latest_author_data { data })
306
23446
        }
307

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
485
22430
        let relay_chain_state = RCSP::current_relay_chain_state();
486
22430
        let relay_storage_root = relay_chain_state.state_root;
487
22430

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

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