Skip to content

Commit

Permalink
Add pallet-storage-roots (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpolaczyk authored Jan 16, 2024
1 parent b5ec213 commit 8406e80
Show file tree
Hide file tree
Showing 7 changed files with 749 additions and 0 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

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

57 changes: 57 additions & 0 deletions pallets/relay-storage-roots/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[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-pallet-parachain-system = { workspace = true }
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-pallet-parachain-system/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 = [
"cumulus-pallet-parachain-system/runtime-benchmarks",
"frame-benchmarking",
]
try-runtime = [ "frame-support/try-runtime" ]
84 changes: 84 additions & 0 deletions pallets/relay-storage-roots/src/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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::{Config, Pallet, RelayStorageRoot, RelayStorageRootKeys};
use cumulus_pallet_parachain_system::{RelayChainState, RelaychainStateProvider};
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
use frame_support::traits::Get;
use sp_core::H256;

fn fill_relay_storage_roots<T: Config>() {
for i in 0..T::MaxStorageRoots::get() {
let relay_state = RelayChainState {
number: i,
state_root: H256::default(),
};
T::RelaychainStateProvider::set_current_relay_chain_state(relay_state);
Pallet::<T>::set_relay_storage_root();
}

assert!(
u32::try_from(RelayStorageRootKeys::<T>::get().len()).unwrap_or(u32::MAX)
>= 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_state = RelayChainState {
number: 1000,
state_root: H256::default(),
};

T::RelaychainStateProvider::set_current_relay_chain_state(relay_state.clone());
}: {
Pallet::<T>::set_relay_storage_root()
}
verify {
assert_eq!(
RelayStorageRoot::<T>::get(
relay_state.number
),
Some(relay_state.state_root)
);
}
}

#[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
);
145 changes: 145 additions & 0 deletions pallets/relay-storage-roots/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// 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/>.

//! # Relay Storage Roots Pallet
//!
//! This pallet stores the latest `MaxStorageRoots` relay storage roots, which can be used to
//! verify state proofs against an old state of the relay chain.
//!
//! This is useful when the proof needs to be generated by an end user, because
//! by the time their transaction is included in a block, the latest relay
//! block will probably have changed and therefore the proof will be invalid.
//! To avoid that, we expect the user to generate a proof against the latest relay block stored
//! in this pallet. This proof will then be valid as long as that relay block is not removed from
//! here.
//!
//! This pallet SHOULD NOT be used for data that can change quickly, because we allow the user to
//! submit a proof of an old state. Therefore a valid proof does not imply that the current relay
//! state is the expected one.
//!
//! ### Design
//!
//! The relay storage roots are inserted in the `on_finalize` hook, so the storage root of the
//! current relay block will not be available in the mapping until the next block, but it can be
//! read using the `RelaychainStateProvider` at any point after the `on_initialize` of
//! `cumulus_pallet_parachain_system`.
//!
//! One storage root is inserted per parachain block, but there may be more than one relay block in
//! between two parachain blocks. In that case, there will be a gap in the `RelayStorageRoot`
//! mapping. When creating a proof, users should ensure that they are using the latest storage root
//! available in the mapping, otherwise it may not be possible to validate their proof.
//!
//! The `RelayStorageRoot` mapping is bounded by `MaxStorageRoots`. To ensure that oldest storage
//! roots are removed first, there is an additional `RelayStorageRootKeys` storage item that stores
//! a sorted list of all the keys. This is needed because it is not possible to iterate over a
//! mapping in order (unless if using an `Identity` hash). The `MaxStorageRoots` limit applies to
//! the number of items, not to their age. Decreasing the value of `MaxStorageRoots` is a breaking
//! change and needs a migration to clean the `RelayStorageRoots` mapping.
#![cfg_attr(not(feature = "std"), no_std)]

pub use crate::weights::WeightInfo;
use cumulus_pallet_parachain_system::RelaychainStateProvider;
use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber;
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_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::*;

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

/// Configuration trait of this pallet.
#[pallet::config]
pub trait Config: frame_system::Config {
type RelaychainStateProvider: RelaychainStateProvider;
/// Limit the number of relay storage roots that will be stored.
/// This limit applies to the number of items, not to their age. Decreasing the value of
/// `MaxStorageRoots` is a breaking change and needs a migration to clean the
/// `RelayStorageRoots` mapping.
#[pallet::constant]
type MaxStorageRoots: Get<u32>;
/// Weight info
type WeightInfo: WeightInfo;
}

/// 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<_, BoundedVec<RelayBlockNumber, T::MaxStorageRoots>, ValueQuery>;

impl<T: Config> Pallet<T> {
/// Populates `RelayStorageRoot` using `RelaychainStateProvider`.
pub fn set_relay_storage_root() {
let relay_state = T::RelaychainStateProvider::current_relay_chain_state();

// If this relay block number has already been stored, skip it.
if <RelayStorageRoot<T>>::contains_key(relay_state.number) {
return;
}

<RelayStorageRoot<T>>::insert(relay_state.number, relay_state.state_root);
let mut keys: VecDeque<_> = <RelayStorageRootKeys<T>>::get().into_inner().into();
if keys.is_empty() && <RelayStorageRootKeys<T>>::exists() {
// If it is empty but it exists in storage, it most likely is corrupted.
log::error!("Corrupted storage `RelayStorageRootKeys`. Need to manually cleanup entries from `RelayStorageRoot` map.");
}
keys.push_back(relay_state.number);
// Delete the oldest stored root if the total number is greater than MaxStorageRoots
if u32::try_from(keys.len()).unwrap_or(u32::MAX) > T::MaxStorageRoots::get() {
if let Some(first_key) = keys.pop_front() {
<RelayStorageRoot<T>>::remove(first_key);
}
}

let keys = BoundedVec::truncate_from(keys.into());
<RelayStorageRootKeys<T>>::put(keys);
}
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
// Account for weight used in on_finalize
T::WeightInfo::set_relay_storage_root()
}
fn on_finalize(_now: BlockNumberFor<T>) {
Self::set_relay_storage_root();
}
}
}
Loading

0 comments on commit 8406e80

Please sign in to comment.