Skip to content

Commit

Permalink
Add pallet-storage-roots
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpolaczyk committed Dec 19, 2023
1 parent de13eac commit eabbfef
Show file tree
Hide file tree
Showing 8 changed files with 979 additions and 0 deletions.
24 changes: 24 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"pallets/migrations",
"pallets/maintenance-mode",
"pallets/randomness",
"pallets/relay-storage-roots",
"precompiles/balances-erc20",
"precompiles/batch",
"precompiles/call-permit",
Expand Down
54 changes: 54 additions & 0 deletions pallets/relay-storage-roots/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[package]
name = "pallet-relay-storage-roots"
authors = { workspace = true }
description = "Stores the last N relay storage roots"
edition = "2021"
version = "0.1.0"

[dependencies]
cumulus-primitives-core = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
environmental = { workspace = true }
log = { workspace = true }
nimbus-primitives = { workspace = true }
parity-scale-codec = { workspace = true, features = [ "derive" ] }
scale-info = { workspace = true, features = [ "derive" ] }
serde = { workspace = true, optional = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

# Benchmarks
frame-benchmarking = { workspace = true, optional = true }
hex = { workspace = true }


[dev-dependencies]
derive_more = { workspace = true }
pallet-author-mapping = { workspace = true, features = [ "std" ] }
pallet-balances = { workspace = true, features = [ "std", "insecure_zero_ed" ] }

[features]
default = [ "std" ]
std = [
"cumulus-primitives-core/std",
"environmental/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"hex/std",
"nimbus-primitives/std",
"parity-scale-codec/std",
"scale-info/std",
"serde",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking",
]
try-runtime = [ "frame-support/try-runtime" ]
93 changes: 93 additions & 0 deletions pallets/relay-storage-roots/src/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright Moonsong Labs
// This file is part of Moonkit.

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

// Moonkit 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 Moonkit. If not, see <http://www.gnu.org/licenses/>.

#![cfg(feature = "runtime-benchmarks")]

//! Benchmarking
use crate::{
Call, Config, InherentIncluded, Pallet, PersistedValidationData, RelayStorageRoot,
RelayStorageRootKeys,
};
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
use frame_support::{assert_ok, traits::Get};
use frame_system::RawOrigin;
use sp_core::H256;

fn fill_relay_storage_roots<T: Config>() {
for i in 0..T::MaxStorageRoots::get() {
let relay_parent_number = i;
let relay_parent_storage_root = H256::default();
let validation_data: PersistedValidationData = PersistedValidationData {
relay_parent_number,
relay_parent_storage_root,
..Default::default()
};

frame_support::storage::unhashed::put(b"MOCK_PERSISTED_VALIDATION_DATA", &validation_data);
assert_ok!(Pallet::<T>::set_relay_storage_root(RawOrigin::None.into()));
}

assert!(
u32::try_from(RelayStorageRootKeys::<T>::get().len()).unwrap() >= T::MaxStorageRoots::get()
);
}

benchmarks! {
// Benchmark for inherent included in every block
set_relay_storage_root {
// Worst case is when `RelayStorageRoot` has len of `MaxStorageRoots`
fill_relay_storage_roots::<T>();

let relay_parent_number = 1000;
let relay_parent_storage_root = H256::default();
let validation_data: PersistedValidationData = PersistedValidationData {
relay_parent_number,
relay_parent_storage_root,
..Default::default()
};
frame_support::storage::unhashed::put(b"MOCK_PERSISTED_VALIDATION_DATA", &validation_data);
}: _(RawOrigin::None)
verify {
// verify randomness result
assert_eq!(
RelayStorageRoot::<T>::get(
relay_parent_number
),
Some(relay_parent_storage_root)
);
assert!(InherentIncluded::<T>::get().is_some());
}
}

#[cfg(test)]
mod tests {
use crate::mock::Test;
use sp_io::TestExternalities;
use sp_runtime::BuildStorage;

pub fn new_test_ext() -> TestExternalities {
let t = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap();
TestExternalities::new(t)
}
}

impl_benchmark_test_suite!(
Pallet,
crate::benchmarks::tests::new_test_ext(),
crate::mock::Test
);
191 changes: 191 additions & 0 deletions pallets/relay-storage-roots/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright Moonsong Labs
// This file is part of Moonkit.

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

// Moonkit 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 Moonkit. If not, see <http://www.gnu.org/licenses/>.

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

pub use crate::weights::WeightInfo;
use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber;
use cumulus_primitives_core::PersistedValidationData;
use frame_support::inherent::IsFatalError;
use frame_support::pallet;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
pub use pallet::*;
use sp_core::Get;
use sp_core::H256;
use sp_runtime::RuntimeString;
use sp_std::collections::vec_deque::VecDeque;

#[cfg(any(test, feature = "runtime-benchmarks"))]
mod benchmarks;
pub mod weights;

#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

#[pallet]
pub mod pallet {
use super::*;

/// The InherentIdentifier "relay storage root"
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"relsroot";

#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);

/// Configuration trait of this pallet.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Overarching event type
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type GetPersistedValidationData: Get<PersistedValidationData>;
#[pallet::constant]
type MaxStorageRoots: Get<u32>;
/// Weight info
type WeightInfo: WeightInfo;
}

#[pallet::error]
pub enum Error<T> {
RequestCounterOverflowed,
}

#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
RequestExpirationExecuted { id: u8 },
}

#[derive(Encode)]
#[cfg_attr(feature = "std", derive(Debug, Decode))]
pub enum InherentError {
Other(RuntimeString),
}

impl IsFatalError for InherentError {
fn is_fatal_error(&self) -> bool {
match *self {
InherentError::Other(_) => true,
}
}
}

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

/// Ensures the mandatory inherent was included in the block
#[pallet::storage]
#[pallet::getter(fn inherent_included)]
pub(crate) type InherentIncluded<T: Config> = StorageValue<_, ()>;

/// Map of relay block number to relay storage root
#[pallet::storage]
pub type RelayStorageRoot<T: Config> =
StorageMap<_, Twox64Concat, RelayBlockNumber, H256, OptionQuery>;

/// List of all the keys in `RelayStorageRoot`.
/// Used to remove the oldest key without having to iterate over all of them.
#[pallet::storage]
pub type RelayStorageRootKeys<T: Config> =
StorageValue<_, VecDeque<RelayBlockNumber>, ValueQuery>;

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Populates `RelayStorageRoot` using this block's `PersistedValidationData`.
#[pallet::call_index(0)]
#[pallet::weight((
T::WeightInfo::set_relay_storage_root(),
DispatchClass::Mandatory
))]
pub fn set_relay_storage_root(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
ensure_none(origin)?;

let validation_data = T::GetPersistedValidationData::get();

if <RelayStorageRoot<T>>::contains_key(validation_data.relay_parent_number) {
<InherentIncluded<T>>::put(());
return Ok(Pays::No.into());
}

<RelayStorageRoot<T>>::insert(
validation_data.relay_parent_number,
validation_data.relay_parent_storage_root,
);

let mut keys = <RelayStorageRootKeys<T>>::get();
keys.push_back(validation_data.relay_parent_number);
// Delete the oldest stored root if the total number is greater than MaxStorageRoots
if u32::try_from(keys.len()).unwrap() > T::MaxStorageRoots::get() {
let first_key = keys.pop_front().unwrap();
<RelayStorageRoot<T>>::remove(first_key);
}

<RelayStorageRootKeys<T>>::put(keys);
<InherentIncluded<T>>::put(());
Ok(Pays::No.into())
}
}

#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = InherentError;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;

fn is_inherent_required(_: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
// Return Ok(Some(_)) unconditionally because this inherent is required in every block
Ok(Some(InherentError::Other(
sp_runtime::RuntimeString::Borrowed("Inherent required to set relay storage roots"),
)))
}

// The empty-payload inherent extrinsic.
fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
Some(Call::set_relay_storage_root {})
}

fn is_inherent(call: &Self::Call) -> bool {
matches!(call, Call::set_relay_storage_root { .. })
}
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
// 1 read and 1 write in on_finalize
T::DbWeight::get().reads_writes(1, 1)
}
fn on_finalize(_now: BlockNumberFor<T>) {
// Ensure the mandatory inherent was included in the block or the block is invalid
assert!(
<InherentIncluded<T>>::take().is_some(),
"Mandatory pallet_relay_storage_roots inherent not included; InherentIncluded storage item is empty"
);
}
}
}
Loading

0 comments on commit eabbfef

Please sign in to comment.