Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Validate timestamps against relay chain slot #437

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pallets/parachain-system/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ xcm = { git = "https://github.com/paritytech/polkadot", default-features = false
# Substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
Expand Down Expand Up @@ -59,6 +60,7 @@ std = [
"codec/std",
"frame-support/std",
"pallet-balances/std",
"pallet-timestamp/std",
"sp-core/std",
"sp-runtime/std",
"sp-io/std",
Expand Down
22 changes: 17 additions & 5 deletions pallets/parachain-system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ use cumulus_primitives_core::{
well_known_keys::{self, NEW_VALIDATION_CODE},
AbridgedHostConfiguration, ChannelStatus, DmpMessageHandler, GetChannelInfo,
InboundDownwardMessage, InboundHrmpMessage, MessageSendError, OnValidationData,
OutboundHrmpMessage, ParaId, PersistedValidationData, UpwardMessage, UpwardMessageSender,
OutboundHrmpMessage, ParaId, PersistedValidationData, Slot, UpwardMessage, UpwardMessageSender,
XcmpMessageHandler, XcmpMessageSource,
};
use cumulus_primitives_parachain_inherent::ParachainInherentData;
use frame_support::{
ensure,
dispatch::{DispatchError, DispatchResult},
ensure,
inherent::{InherentData, InherentIdentifier, ProvideInherent},
storage,
traits::Get,
weights::{PostDispatchInfo, Weight, Pays},
inherent::{InherentData, InherentIdentifier, ProvideInherent},
weights::{Pays, PostDispatchInfo, Weight},
};
use frame_system::{ensure_none, ensure_root};
use polkadot_parachain::primitives::RelayChainBlockNumber;
Expand All @@ -63,6 +63,7 @@ pub mod validate_block;
mod tests;

pub use pallet::*;
pub use validate_block::ValidateTimestampAgainstRelayChainSlot;

#[frame_support::pallet]
pub mod pallet {
Expand Down Expand Up @@ -313,7 +314,7 @@ pub mod pallet {
}
}

let (host_config, relevant_messaging_state) =
let (current_slot, host_config, relevant_messaging_state) =
match relay_state_snapshot::extract_from_proof(
T::SelfParaId::get(),
vfp.relay_parent_storage_root,
Expand All @@ -327,6 +328,7 @@ pub mod pallet {

<ValidationData<T>>::put(&vfp);
<RelevantMessagingState<T>>::put(relevant_messaging_state.clone());
<RelaySlot<T>>::put(current_slot);
<HostConfiguration<T>>::put(host_config);

<T::OnValidationData as OnValidationData>::on_validation_data(&vfp);
Expand Down Expand Up @@ -466,6 +468,16 @@ pub mod pallet {
#[pallet::getter(fn host_configuration)]
pub(super) type HostConfiguration<T: Config> = StorageValue<_, AbridgedHostConfiguration>;

/// The relay chain current slot number that was obtained from the relay parent.
///
/// This field is meant to be updated each block with the validation data inherent. Therefore,
/// before processing of the inherent, e.g. in `on_initialize` this data may be stale.
///
/// This data is also absent from the genesis.
#[pallet::storage]
#[pallet::getter(fn relay_slot)]
pub(super) type RelaySlot<T: Config> = StorageValue<_, Slot>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any reason to add this here as a storage item.


/// The last downward message queue chain head we have observed.
///
/// This value is loaded before and saved after processing inbound downward messages carried
Expand Down
25 changes: 15 additions & 10 deletions pallets/parachain-system/src/relay_state_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.

use codec::{Encode, Decode};
use cumulus_primitives_core::{relay_chain, AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId};
use codec::{Decode, Encode};
use cumulus_primitives_core::{
relay_chain, AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId, Slot,
};
use hash_db::{HashDB, EMPTY_PREFIX};
use sp_runtime::traits::HashFor;
use sp_state_machine::{Backend, TrieBackend};
use sp_trie::StorageProof;
use sp_std::vec::Vec;
use sp_trie::StorageProof;

/// A snapshot of some messaging related state of relay chain pertaining to the current parachain.
///
Expand Down Expand Up @@ -61,6 +63,8 @@ pub struct MessagingStateSnapshot {
pub enum Error {
/// The provided proof was created against unexpected storage root.
RootMismatch,
/// The crreunt slot cannot be extracted.
Slot(ReadEntryErr),
/// The host configuration cannot be extracted.
Config(ReadEntryErr),
/// The DMQ MQC head cannot be extracted.
Expand Down Expand Up @@ -111,19 +115,19 @@ pub fn extract_from_proof(
para_id: ParaId,
relay_parent_storage_root: relay_chain::v1::Hash,
proof: StorageProof,
) -> Result<(AbridgedHostConfiguration, MessagingStateSnapshot), Error> {
) -> Result<(Slot, AbridgedHostConfiguration, MessagingStateSnapshot), Error> {
let db = proof.into_memory_db::<HashFor<relay_chain::Block>>();
if !db.contains(&relay_parent_storage_root, EMPTY_PREFIX) {
return Err(Error::RootMismatch);
}
let backend = TrieBackend::new(db, relay_parent_storage_root);

let host_config: AbridgedHostConfiguration = read_entry(
&backend,
relay_chain::well_known_keys::ACTIVE_CONFIG,
None,
)
.map_err(Error::Config)?;
let current_slot: Slot = read_entry(&backend, relay_chain::well_known_keys::CURRENT_SLOT, None)
.map_err(Error::Slot)?;

let host_config: AbridgedHostConfiguration =
read_entry(&backend, relay_chain::well_known_keys::ACTIVE_CONFIG, None)
.map_err(Error::Config)?;

let dmq_mqc_head: relay_chain::Hash = read_entry(
&backend,
Expand Down Expand Up @@ -187,6 +191,7 @@ pub fn extract_from_proof(
// by relying on the fact that `ingress_channel_index` and `egress_channel_index` are themselves sorted.

Ok((
current_slot,
host_config,
MessagingStateSnapshot {
dmq_mqc_head,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ pub fn validate_block<B: BlockT, E: ExecuteBlock<B>, PSC: crate::Config>(

let validation_data = set_and_run_with_externalities(&mut ext, || {
super::set_and_run_with_validation_params(params, || {
E::execute_block(block);
super::timestamp::run_with_timestamp_validation_params(|| {
E::execute_block(block);
});

ParachainSystem::<PSC>::validation_data()
.expect("`PersistedValidationData` should be set in every block!")
Expand Down
3 changes: 3 additions & 0 deletions pallets/parachain-system/src/validate_block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use polkadot_parachain::primitives::ValidationParams;
#[cfg(not(feature = "std"))]
#[doc(hidden)]
pub mod implementation;
mod timestamp;
#[cfg(test)]
mod tests;

Expand All @@ -31,6 +32,8 @@ pub use polkadot_parachain;
#[doc(hidden)]
pub use sp_runtime::traits::GetRuntimeBlockType;

pub use timestamp::ValidateTimestampAgainstRelayChainSlot;

// Stores the [`ValidationParams`] that are being passed to `validate_block`.
//
// This value will only be set when a parachain validator validates a given `PoV`.
Expand Down
117 changes: 117 additions & 0 deletions pallets/parachain-system/src/validate_block/timestamp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Substrate.

// Substrate 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.

// Substrate 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 <http://www.gnu.org/licenses/>.

//! Utility module to validate cumulus-runtime timestamps against the
//! relay chain slot.

use cumulus_primitives_core::{OnValidationData, Slot};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be its own pallet. Similar to aura-ext. Maybe timestamp-ext or something.

The idea with using environmental was also a bad idea by me. We should probably store the intermediate data in the storage.

use frame_support::traits::Get;

use crate::{Config, PersistedValidationData, RelaySlot};

// Temporary global that holds the state as populated by the hooks `OnTimestampSet` or
// `OnValidationData`.
#[derive(Default)]
pub(crate) struct TimestampValidationParams {
relay_chain_slot: Option<Slot>,
timestamp_slot: Option<Slot>,
}

// Stores the [`TimestampValidationParams`] that are being passed to `validate_block`.
//
// This value will only be set when a parachain validator validates a given `PoV`.
environmental::environmental!(TIMESTAMP_VALIDATION_PARAMS: TimestampValidationParams);

/// Set the [`TimestampValidationParams`] for the local context and execute the given closure in
/// this context.
#[cfg(not(feature = "std"))]
pub(crate) fn run_with_timestamp_validation_params<R>(
f: impl FnOnce() -> R,
) -> R {
let mut params = Default::default();
TIMESTAMP_VALIDATION_PARAMS::using(&mut params, f)
}

/// Utility to validate the timestamps set from a cumulus-enabled runtime against
/// the relay chain slot number. To enable this validation the runtime hooks for
/// `OnTimestampSet` and `OnValidationData` should be set to this struct using the
/// appropriate slot duration of the relay chain.
pub struct ValidateTimestampAgainstRelayChainSlot<
T: pallet_timestamp::Config,
RelaySlotDuration: Get<T::Moment>,
>(sp_std::marker::PhantomData<(T, RelaySlotDuration)>);

impl<T, RelaySlotDuration> OnValidationData
for ValidateTimestampAgainstRelayChainSlot<T, RelaySlotDuration>
where
T: Config + pallet_timestamp::Config,
RelaySlotDuration: Get<T::Moment>,
{
fn on_validation_data(_data: &PersistedValidationData) {
TIMESTAMP_VALIDATION_PARAMS::with(|p| {
let relay_chain_slot = match RelaySlot::<T>::get() {
Some(slot) => slot,
_ => {
// this should be unreachable as the relay slot should always be populated after
// we have processed the validation data.
return;
}
};

if let Some(timestamp_slot) = p.timestamp_slot {
assert!(
valid_slot(relay_chain_slot, timestamp_slot),
"Timestamp slot must be consistent with relay chain slot: relay: {:?}, local: {:?}",
relay_chain_slot,
timestamp_slot,
);
} else {
p.relay_chain_slot = Some(relay_chain_slot);
}
});
}
}

impl<T, RelaySlotDuration> frame_support::traits::OnTimestampSet<T::Moment>
for ValidateTimestampAgainstRelayChainSlot<T, RelaySlotDuration>
where
T: pallet_timestamp::Config,
RelaySlotDuration: Get<T::Moment>,
{
fn on_timestamp_set(moment: T::Moment) {
use sp_runtime::SaturatedConversion;

TIMESTAMP_VALIDATION_PARAMS::with(|p| {
let timestamp_slot = moment / RelaySlotDuration::get();
let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());

if let Some(relay_chain_slot) = p.relay_chain_slot {
assert!(
valid_slot(relay_chain_slot, timestamp_slot),
"Timestamp slot must be consistent with relay chain slot: relay: {:?}, local: {:?}",
relay_chain_slot,
timestamp_slot,
);
} else {
p.timestamp_slot = Some(timestamp_slot);
}
});
}
}

fn valid_slot(relay_slot: Slot, local_slot: Slot) -> bool {
relay_slot == local_slot || relay_slot + 1 == local_slot
}
11 changes: 9 additions & 2 deletions polkadot-parachains/rococo-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,16 @@ impl frame_system::Config for Runtime {

parameter_types! {
pub const MinimumPeriod: u64 = SLOT_DURATION / 2;
pub const RelaySlotDuration: u64 = SLOT_DURATION / 2;
}

impl pallet_timestamp::Config for Runtime {
/// A timestamp: milliseconds since the unix epoch.
type Moment = u64;
type OnTimestampSet = ();
type OnTimestampSet = cumulus_pallet_parachain_system::ValidateTimestampAgainstRelayChainSlot<
Runtime,
RelaySlotDuration,
>;
type MinimumPeriod = MinimumPeriod;
type WeightInfo = ();
}
Expand Down Expand Up @@ -238,7 +242,10 @@ parameter_types! {

impl cumulus_pallet_parachain_system::Config for Runtime {
type Event = Event;
type OnValidationData = ();
type OnValidationData = cumulus_pallet_parachain_system::ValidateTimestampAgainstRelayChainSlot<
Runtime,
RelaySlotDuration,
>;
type SelfParaId = parachain_info::Pallet<Runtime>;
type OutboundXcmpMessageSource = XcmpQueue;
type DmpMessageHandler = DmpQueue;
Expand Down
11 changes: 5 additions & 6 deletions primitives/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@

#![cfg_attr(not(feature = "std"), no_std)]

use sp_std::prelude::*;
use codec::{Encode, Decode};
use sp_runtime::{RuntimeDebug, traits::Block as BlockT};
use codec::{Decode, Encode};
use frame_support::weights::Weight;
use sp_runtime::{traits::Block as BlockT, RuntimeDebug};
use sp_std::prelude::*;

pub use polkadot_core_primitives::InboundDownwardMessage;
pub use polkadot_parachain::primitives::{Id as ParaId, UpwardMessage, ValidationParams};
pub use polkadot_primitives::v1::{
PersistedValidationData, AbridgedHostConfiguration, AbridgedHrmpChannel,
AbridgedHostConfiguration, AbridgedHrmpChannel, PersistedValidationData, Slot,
};

/// A module that re-exports relevant relay chain definitions.
pub mod relay_chain {
pub use polkadot_core_primitives::*;
pub use polkadot_primitives::v1;
pub use polkadot_primitives::v1::well_known_keys;
pub use polkadot_primitives::{v1, v1::well_known_keys};
}
use relay_chain::BlockNumber as RelayBlockNumber;

Expand Down
1 change: 1 addition & 0 deletions primitives/parachain-inherent/src/client_side.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ fn collect_relay_storage_proof(
.unwrap_or_default();

let mut relevant_keys = vec![];
relevant_keys.push(relay_well_known_keys::CURRENT_SLOT.to_vec());
relevant_keys.push(relay_well_known_keys::ACTIVE_CONFIG.to_vec());
relevant_keys.push(relay_well_known_keys::dmq_mqc_head(para_id));
relevant_keys.push(relay_well_known_keys::relay_dispatch_queue_size(para_id));
Expand Down
3 changes: 2 additions & 1 deletion primitives/parachain-inherent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ pub struct ParachainInherentData {
///
/// Specifically this witness contains the data for:
///
/// - active host configuration as per the relay parent,
/// - the current slot number at the given relay parent
/// - active host configuration as per the relay parent
/// - the relay dispatch queue sizes
/// - the list of egress HRMP channels (in the list of recipients form)
/// - the metadata for the egress HRMP channels
Expand Down