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
pub use dp_chain_state_snapshot::*;
34
use {
35
    cumulus_pallet_parachain_system::RelaychainStateProvider,
36
    cumulus_primitives_core::{
37
        relay_chain::{BlakeTwo256, BlockNumber, HeadData},
38
        ParaId,
39
    },
40
    dp_core::well_known_keys::PARAS_HEADS_INDEX,
41
    frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*, Hashable},
42
    frame_system::pallet_prelude::*,
43
    nimbus_primitives::SlotBeacon,
44
    parity_scale_codec::{Decode, Encode},
45
    sp_consensus_aura::{inherents::InherentType, Slot, AURA_ENGINE_ID},
46
    sp_inherents::{InherentIdentifier, IsFatalError},
47
    sp_runtime::{traits::Header, DigestItem, DispatchResult, RuntimeString},
48
    tp_author_noting_inherent::INHERENT_IDENTIFIER,
49
    tp_traits::{
50
        AuthorNotingHook, ContainerChainBlockInfo, GetContainerChainAuthor,
51
        GetCurrentContainerChains,
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
use tp_traits::LatestAuthorInfoFetcher;
70

            
71
12008
#[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 SelfParaId: Get<ParaId>;
83
        type SlotBeacon: SlotBeacon;
84

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

            
87
        type RelayChainStateProvider: cumulus_pallet_parachain_system::RelaychainStateProvider;
88

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

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

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

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

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

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

            
121
6821
            weight += T::DbWeight::get().writes(1);
122
6821

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

            
126
6821
            weight
127
6821
        }
128

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

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

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

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

            
156
6853
            // We do this first to make sure we don't do 2 reads (parachains and relay state)
157
6853
            // when we have no containers registered
158
6853
            // Essentially one can pass an empty proof if no container-chains are registered
159
6853
            if !registered_para_ids.is_empty() {
160
                let tp_author_noting_inherent::OwnParachainInherentData {
161
6847
                    relay_storage_proof,
162
6847
                } = data;
163
6847

            
164
6847
                let relay_chain_state = T::RelayChainStateProvider::current_relay_chain_state();
165
6847
                let relay_storage_root = relay_chain_state.state_root;
166
6847
                let relay_storage_rooted_proof =
167
6847
                    GenericStateProof::new(relay_storage_root, relay_storage_proof)
168
6847
                        .expect("Invalid relay chain state proof");
169
6847
                let parent_tanssi_slot = u64::from(T::SlotBeacon::slot()).into();
170

            
171
                // TODO: we should probably fetch all authors-containers first
172
                // then pass the vector to the hook, this would allow for a better estimation
173
20327
                for para_id in registered_para_ids {
174
13484
                    match Self::fetch_block_info_from_proof(
175
13484
                        &relay_storage_rooted_proof,
176
13484
                        para_id,
177
13484
                        parent_tanssi_slot,
178
13484
                    ) {
179
6586
                        Ok(block_info) => {
180
6586
                            LatestAuthor::<T>::mutate(
181
6586
                                para_id,
182
6586
                                |maybe_old_block_info: &mut Option<
183
                                    ContainerChainBlockInfo<T::AccountId>,
184
6586
                                >| {
185
6586
                                    if let Some(ref mut old_block_info) = maybe_old_block_info {
186
6389
                                        if block_info.block_number > old_block_info.block_number {
187
6387
                                            // We only reward author if the block increases
188
6387
                                            total_weight = total_weight.saturating_add(
189
6387
                                                T::AuthorNotingHook::on_container_author_noted(
190
6387
                                                    &block_info.author,
191
6387
                                                    block_info.block_number,
192
6387
                                                    para_id,
193
6387
                                                ),
194
6387
                                            );
195
6387
                                            let _ = core::mem::replace(old_block_info, block_info);
196
6387
                                        }
197
197
                                    } else {
198
197
                                        // If there is no previous block, we should reward the author of the first block
199
197
                                        total_weight = total_weight.saturating_add(
200
197
                                            T::AuthorNotingHook::on_container_author_noted(
201
197
                                                &block_info.author,
202
197
                                                block_info.block_number,
203
197
                                                para_id,
204
197
                                            ),
205
197
                                        );
206
197
                                        let _ = core::mem::replace(
207
197
                                            maybe_old_block_info,
208
197
                                            Some(block_info),
209
197
                                        );
210
197
                                    }
211
6586
                                },
212
6586
                            );
213
6586
                        }
214
6898
                        Err(e) => log::warn!(
215
6882
                            "Author-noting error {:?} found in para {:?}",
216
6882
                            e,
217
6882
                            u32::from(para_id)
218
                        ),
219
                    }
220
                }
221
6
            }
222

            
223
            // We correctly set the data
224
6849
            DidSetContainerAuthorData::<T>::put(true);
225
6849

            
226
6849
            Ok(PostDispatchInfo {
227
6849
                actual_weight: Some(total_weight),
228
6849
                pays_fee: Pays::No,
229
6849
            })
230
        }
231

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

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

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

            
283
6714
    #[pallet::storage]
284
    #[pallet::getter(fn latest_author)]
285
    pub(super) type LatestAuthor<T: Config> =
286
        StorageMap<_, Blake2_128Concat, ParaId, ContainerChainBlockInfo<T::AccountId>, OptionQuery>;
287

            
288
    /// Was the containerAuthorData set?
289
54684
    #[pallet::storage]
290
    pub(super) type DidSetContainerAuthorData<T: Config> = StorageValue<_, bool, ValueQuery>;
291

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

            
300
        fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
301
            // Return Ok(Some(_)) unconditionally because this inherent is required in every block
302
            Ok(Some(InherentError::Other(
303
                sp_runtime::RuntimeString::Borrowed("Pallet Author Noting Inherent required"),
304
            )))
305
        }
306

            
307
6822
        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
308
6822
            let data: tp_author_noting_inherent::OwnParachainInherentData = data
309
6822
                .get_data(&INHERENT_IDENTIFIER)
310
6822
                .ok()
311
6822
                .flatten()
312
6822
                .expect("there is not data to be posted; qed");
313
6822

            
314
6822
            Some(Call::set_latest_author_data { data })
315
6822
        }
316

            
317
6806
        fn is_inherent(call: &Self::Call) -> bool {
318
6806
            matches!(call, Call::set_latest_author_data { .. })
319
6806
        }
320
    }
321
}
322

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

            
346
        // We later take the Header decoded
347
13474
        let author_header = sp_runtime::generic::Header::<BlockNumber, BlakeTwo256>::decode(
348
13474
            &mut head_data.0.as_slice(),
349
13474
        )
350
13474
        .map_err(|_| Error::<T>::FailedDecodingHeader)?;
351

            
352
        // Return author from first aura log.
353
        // If there are no aura logs, it iterates over all the logs, then returns the error from the first element.
354
        // This is because it is hard to return a `Vec<Error<T>>`.
355
13473
        let mut first_error = None;
356
13473
        for aura_digest in author_header.digest().logs() {
357
13471
            match Self::author_from_log(aura_digest, para_id, &author_header, tanssi_slot) {
358
6586
                Ok(x) => return Ok(x),
359
6885
                Err(e) => {
360
6885
                    if first_error.is_none() {
361
6885
                        first_error = Some(e);
362
6885
                    }
363
                }
364
            }
365
        }
366

            
367
6884
        Err(first_error.unwrap_or(Error::<T>::AuraDigestFirstItem))
368
13480
    }
369

            
370
    /// Get block author from aura digest
371
13471
    fn author_from_log(
372
13471
        aura_digest: &DigestItem,
373
13471
        para_id: ParaId,
374
13471
        author_header: &sp_runtime::generic::Header<BlockNumber, BlakeTwo256>,
375
13471
        tanssi_slot: Slot,
376
13471
    ) -> Result<ContainerChainBlockInfo<T::AccountId>, Error<T>> {
377
        // We decode the digest as pre-runtime digest
378
13471
        let (id, mut data) = aura_digest
379
13471
            .as_pre_runtime()
380
13471
            .ok_or(Error::<T>::AsPreRuntimeError)?;
381

            
382
        // Match against the Aura digest
383
13471
        if id == AURA_ENGINE_ID {
384
            // DecodeSlot
385
13469
            let slot = InherentType::decode(&mut data).map_err(|_| Error::<T>::NonDecodableSlot)?;
386

            
387
            // Fetch Author
388
13468
            let author = T::ContainerChainAuthor::author_for_slot(slot, para_id)
389
13468
                .ok_or(Error::<T>::AuthorNotFound)?;
390

            
391
6586
            Ok(ContainerChainBlockInfo {
392
6586
                block_number: author_header.number,
393
6586
                author,
394
6586
                // We store the slot number of the current tanssi block to have a time-based notion
395
6586
                // of when the last block of a container chain was included.
396
6586
                // Note that this is not the slot of the container chain block, and it does not
397
6586
                // indicate when that block was created, but when it was included in tanssi.
398
6586
                latest_slot_number: tanssi_slot,
399
6586
            })
400
        } else {
401
2
            Err(Error::<T>::NonAuraDigest)
402
        }
403
13471
    }
404
}
405

            
406
#[derive(Encode)]
407
#[cfg_attr(feature = "std", derive(Debug, Decode))]
408
pub enum InherentError {
409
    Other(RuntimeString),
410
}
411

            
412
impl IsFatalError for InherentError {
413
    fn is_fatal_error(&self) -> bool {
414
        match *self {
415
            InherentError::Other(_) => true,
416
        }
417
    }
418
}
419

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

            
432
impl<T: Config> LatestAuthorInfoFetcher<T::AccountId> for Pallet<T> {
433
16
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<T::AccountId>> {
434
16
        LatestAuthor::<T>::get(para_id)
435
16
    }
436
}