diff --git a/Cargo.lock b/Cargo.lock index 0d953383686..e5ab529a67a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2877,6 +2877,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "cumulus-primitives-timestamp" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "futures", + "parity-scale-codec 3.6.4", + "sp-inherents", + "sp-std", + "sp-timestamp", +] + [[package]] name = "cumulus-primitives-utility" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7981dc816cd..b0899a03bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "parachain-template/runtime", "primitives/core", "primitives/parachain-inherent", + "primitives/timestamp", "primitives/utility", "polkadot-parachain", "parachains/common", diff --git a/pallets/parachain-system/proc-macro/src/lib.rs b/pallets/parachain-system/proc-macro/src/lib.rs index 3c6fb21e5f3..70c6857120c 100644 --- a/pallets/parachain-system/proc-macro/src/lib.rs +++ b/pallets/parachain-system/proc-macro/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use proc_macro2::{Span, TokenStream}; +use proc_macro2::Span; use proc_macro_crate::{crate_name, FoundCrate}; use syn::{ parse::{Parse, ParseStream}, @@ -25,17 +25,20 @@ use syn::{ mod keywords { syn::custom_keyword!(Runtime); syn::custom_keyword!(BlockExecutor); + syn::custom_keyword!(CheckInherents); } struct Input { runtime: Path, block_executor: Path, + check_inherents: Option, } impl Parse for Input { fn parse(input: ParseStream) -> Result { let mut runtime = None; let mut block_executor = None; + let mut check_inherents = None; fn parse_inner( input: ParseStream, @@ -56,26 +59,24 @@ impl Parse for Input { } } - while runtime.is_none() || block_executor.is_none() { + while !input.is_empty() || runtime.is_none() || block_executor.is_none() { let lookahead = input.lookahead1(); if lookahead.peek(keywords::Runtime) { parse_inner::(input, &mut runtime)?; } else if lookahead.peek(keywords::BlockExecutor) { parse_inner::(input, &mut block_executor)?; + } else if lookahead.peek(keywords::CheckInherents) { + parse_inner::(input, &mut check_inherents)?; } else { return Err(lookahead.error()) } } - let rest = input.parse::()?; - if !rest.is_empty() { - return Err(Error::new(rest.span(), "Unexpected input data")) - } - Ok(Self { runtime: runtime.expect("Everything is parsed before; qed"), block_executor: block_executor.expect("Everything is parsed before; qed"), + check_inherents, }) } } @@ -91,7 +92,7 @@ fn crate_() -> Result { #[proc_macro] pub fn register_validate_block(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let Input { runtime, block_executor } = match syn::parse(input) { + let Input { runtime, block_executor, check_inherents } = match syn::parse(input) { Ok(t) => t, Err(e) => return e.into_compile_error().into(), }; @@ -101,6 +102,17 @@ pub fn register_validate_block(input: proc_macro::TokenStream) -> proc_macro::To Err(e) => return e.into_compile_error().into(), }; + let check_inherents = match check_inherents { + Some(_check_inherents) => { + quote::quote! { #_check_inherents } + }, + None => { + quote::quote! { + #crate_::DummyCheckInherents<<#runtime as #crate_::validate_block::GetRuntimeBlockType>::RuntimeBlock> + } + }, + }; + if cfg!(not(feature = "std")) { quote::quote! { #[doc(hidden)] @@ -127,6 +139,7 @@ pub fn register_validate_block(input: proc_macro::TokenStream) -> proc_macro::To <#runtime as #crate_::validate_block::GetRuntimeBlockType>::RuntimeBlock, #block_executor, #runtime, + #check_inherents, >(params); #crate_::validate_block::polkadot_parachain::write_result(&res) diff --git a/pallets/parachain-system/src/lib.rs b/pallets/parachain-system/src/lib.rs index 83954995678..648e6e90cd4 100644 --- a/pallets/parachain-system/src/lib.rs +++ b/pallets/parachain-system/src/lib.rs @@ -48,7 +48,7 @@ use frame_system::{ensure_none, ensure_root, pallet_prelude::HeaderFor}; use polkadot_parachain::primitives::RelayChainBlockNumber; use scale_info::TypeInfo; use sp_runtime::{ - traits::{BlockNumberProvider, Hash}, + traits::{Block as BlockT, BlockNumberProvider, Hash}, transaction_validity::{ InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity, ValidTransaction, @@ -86,10 +86,12 @@ pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; /// ``` /// struct BlockExecutor; /// struct Runtime; +/// struct CheckInherents; /// /// cumulus_pallet_parachain_system::register_validate_block! { /// Runtime = Runtime, /// BlockExecutor = Executive, +/// CheckInherents = CheckInherents, /// } /// /// # fn main() {} @@ -1497,6 +1499,36 @@ impl UpwardMessageSender for Pallet { } } +/// Something that can check the inherents of a block. +#[cfg_attr( + feature = "parameterized-consensus-hook", + deprecated = "consider switching to `cumulus-pallet-parachain-system::ConsensusHook`" +)] +pub trait CheckInherents { + /// Check all inherents of the block. + /// + /// This function gets passed all the extrinsics of the block, so it is up to the callee to + /// identify the inherents. The `validation_data` can be used to access the + fn check_inherents( + block: &Block, + validation_data: &RelayChainStateProof, + ) -> frame_support::inherent::CheckInherentsResult; +} + +/// Struct that always returns `Ok` on inherents check, needed for backwards-compatibility. +#[doc(hidden)] +pub struct DummyCheckInherents(sp_std::marker::PhantomData); + +#[allow(deprecated)] +impl CheckInherents for DummyCheckInherents { + fn check_inherents( + _: &Block, + _: &RelayChainStateProof, + ) -> frame_support::inherent::CheckInherentsResult { + sp_inherents::CheckInherentsResult::new() + } +} + /// Something that should be informed about system related events. /// /// This includes events like [`on_validation_data`](Self::on_validation_data) that is being diff --git a/pallets/parachain-system/src/validate_block/implementation.rs b/pallets/parachain-system/src/validate_block/implementation.rs index 6945362394f..4f07f2021c3 100644 --- a/pallets/parachain-system/src/validate_block/implementation.rs +++ b/pallets/parachain-system/src/validate_block/implementation.rs @@ -65,12 +65,20 @@ fn with_externalities R, R>(f: F) -> R { /// we have the in-memory database that contains all the values from the state of the parachain /// that we require to verify the block. /// -/// 5. The last step is to execute the entire block in the machinery we just have setup. Executing +/// 5. We are going to run `check_inherents`. This is important to check stuff like the timestamp +/// matching the real world time. +/// +/// 6. The last step is to execute the entire block in the machinery we just have setup. Executing /// the blocks include running all transactions in the block against our in-memory database and /// ensuring that the final storage root matches the storage root in the header of the block. In the /// end we return back the [`ValidationResult`] with all the required information for the validator. #[doc(hidden)] -pub fn validate_block, PSC: crate::Config>( +pub fn validate_block< + B: BlockT, + E: ExecuteBlock, + PSC: crate::Config, + CI: crate::CheckInherents, +>( MemoryOptimizedValidationParams { block_data, parent_head, @@ -159,6 +167,27 @@ where sp_io::offchain_index::host_clear.replace_implementation(host_offchain_index_clear), ); + run_with_externalities::(&backend, || { + let relay_chain_proof = crate::RelayChainStateProof::new( + PSC::SelfParaId::get(), + inherent_data.validation_data.relay_parent_storage_root, + inherent_data.relay_chain_state.clone(), + ) + .expect("Invalid relay chain state proof"); + + let res = CI::check_inherents(&block, &relay_chain_proof); + + if !res.ok() { + if log::log_enabled!(log::Level::Error) { + res.into_errors().for_each(|e| { + log::error!("Checking inherent with identifier `{:?}` failed", e.0) + }); + } + + panic!("Checking inherents failed"); + } + }); + run_with_externalities::(&backend, || { let head_data = HeadData(block.header().encode()); diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml new file mode 100644 index 00000000000..176f22fe5d3 --- /dev/null +++ b/primitives/timestamp/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cumulus-primitives-timestamp" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +description = "Provides timestamp related functionality for parachains." + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive" ] } +futures = "0.3.28" + +# Substrate +sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } + +# Cumulus +cumulus-primitives-core = { path = "../core", default-features = false } + +[features] +default = [ "std" ] +std = [ + "sp-inherents/std", + "sp-std/std", + "sp-timestamp/std", + "cumulus-primitives-core/std", +] diff --git a/primitives/timestamp/src/lib.rs b/primitives/timestamp/src/lib.rs new file mode 100644 index 00000000000..5af3ddf21dc --- /dev/null +++ b/primitives/timestamp/src/lib.rs @@ -0,0 +1,70 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Cumulus timestamp related primitives. +//! +//! Provides a [`InherentDataProvider`] that should be used in the validation phase of the parachain. +//! It will be used to create the inherent data and that will be used to check the inherents inside +//! the parachain block (in this case the timestamp inherent). As we don't have access to any clock +//! from the runtime the timestamp is always passed as an inherent into the runtime. To check this +//! inherent when validating the block, we will use the relay chain slot. As the relay chain slot +//! is derived from a timestamp, we can easily convert it back to a timestamp by muliplying it with +//! the slot duration. By comparing the relay chain slot derived timestamp with the timestamp we can +//! ensure that the parachain timestamp is reasonable. + +#![cfg_attr(not(feature = "std"), no_std)] + +use cumulus_primitives_core::relay_chain::Slot; +use sp_inherents::{Error, InherentData}; +use sp_std::time::Duration; + +pub use sp_timestamp::{InherentType, INHERENT_IDENTIFIER}; + +/// The inherent data provider for the timestamp. +/// +/// This should be used in the runtime when checking the inherents in the validation phase of the +/// parachain. +pub struct InherentDataProvider { + relay_chain_slot: Slot, + relay_chain_slot_duration: Duration, +} + +impl InherentDataProvider { + /// Create `Self` from the given relay chain slot and slot duration. + pub fn from_relay_chain_slot_and_duration( + relay_chain_slot: Slot, + relay_chain_slot_duration: Duration, + ) -> Self { + Self { relay_chain_slot, relay_chain_slot_duration } + } + + /// Create the inherent data. + pub fn create_inherent_data(&self) -> Result { + let mut inherent_data = InherentData::new(); + self.provide_inherent_data(&mut inherent_data).map(|_| inherent_data) + } + + /// Provide the inherent data into the given `inherent_data`. + pub fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + // As the parachain starts building at around `relay_chain_slot + 1` we use that slot to + // calculate the timestamp. + let data: InherentType = ((*self.relay_chain_slot + 1) * + self.relay_chain_slot_duration.as_millis() as u64) + .into(); + + inherent_data.put_data(INHERENT_IDENTIFIER, &data) + } +}