Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
280d358
Added `relayed_incoming_message_proofs_works` test-cases
bkontur Apr 12, 2025
f7388f2
Add simple proof root store (`pallet-bridge-proof-root-store`)
bkontur Apr 14, 2025
99f848f
Setup message proof validation with `pallet-bridge-proof-root-store` …
bkontur Apr 14, 2025
5b70690
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur Apr 24, 2025
296b387
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Apr 24, 2025
0a05cca
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur Apr 25, 2025
3a55ba8
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur Apr 29, 2025
26c3f27
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur Apr 29, 2025
f619494
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur May 26, 2025
25b7df9
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur May 28, 2025
e61c62c
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur May 28, 2025
3aa761d
Update bridges/modules/proof-root-store/src/lib.rs
bkontur May 28, 2025
9cd9507
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur Jul 10, 2025
b76a9b8
Merge remote-tracking branch 'origin/bko-permlanes-on-ahs' into bko-p…
bkontur Jul 16, 2025
340bfe7
Merge remote-tracking branch 'origin/bko-permlanes-on-ahs' into bko-p…
bkontur Jul 18, 2025
578de6f
Refactor ring buffer - align with pallet-bridge-grandpa
bkontur Jul 18, 2025
426b0c1
Merge branch 'bko-permlanes-on-ahs' into bko-permlanes-on-ahs-1-heads…
bkontur Jul 21, 2025
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
26 changes: 26 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"bridges/modules/grandpa",
"bridges/modules/messages",
"bridges/modules/parachains",
"bridges/modules/proof-root-store",
"bridges/modules/relayers",
"bridges/modules/xcm-bridge",
"bridges/modules/xcm-bridge-hub",
Expand All @@ -26,6 +27,7 @@ members = [
"bridges/primitives/messages",
"bridges/primitives/parachains",
"bridges/primitives/polkadot-core",
"bridges/primitives/proof-root-store",
"bridges/primitives/relayers",
"bridges/primitives/runtime",
"bridges/primitives/test-utils",
Expand Down Expand Up @@ -692,6 +694,7 @@ bp-messages = { path = "bridges/primitives/messages", default-features = false }
bp-parachains = { path = "bridges/primitives/parachains", default-features = false }
bp-polkadot-bulletin = { path = "bridges/chains/chain-polkadot-bulletin", default-features = false }
bp-polkadot-core = { path = "bridges/primitives/polkadot-core", default-features = false }
bp-proof-root-store = { path = "bridges/primitives/proof-root-store", default-features = false }
bp-relayers = { path = "bridges/primitives/relayers", default-features = false }
bp-rococo = { path = "polkadot/runtime/rococo/bridge-primitives", default-features = false }
bp-runtime = { path = "bridges/primitives/runtime", default-features = false }
Expand Down Expand Up @@ -963,6 +966,7 @@ pallet-bounties = { path = "substrate/frame/bounties", default-features = false
pallet-bridge-grandpa = { path = "bridges/modules/grandpa", default-features = false }
pallet-bridge-messages = { path = "bridges/modules/messages", default-features = false }
pallet-bridge-parachains = { path = "bridges/modules/parachains", default-features = false }
pallet-bridge-proof-root-store = { path = "bridges/modules/proof-root-store", default-features = false }
pallet-bridge-relayers = { path = "bridges/modules/relayers", default-features = false }
pallet-broker = { path = "substrate/frame/broker", default-features = false }
pallet-child-bounties = { path = "substrate/frame/child-bounties", default-features = false }
Expand Down
44 changes: 44 additions & 0 deletions bridges/modules/proof-root-store/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "pallet-bridge-proof-root-store"
version = "0.1.0"
description = "Module that allows bridged relay chains to exchange information on their parachains' heads."
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[lints]
workspace = true

[dependencies]
codec = { features = ["derive"], workspace = true }
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
scale-info = { features = ["derive"], workspace = true }

[dev-dependencies]
bp-proof-root-store = { workspace = true }
sp-io = { workspace = true }
substrate-test-utils = { workspace = true }

[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
]
187 changes: 187 additions & 0 deletions bridges/modules/proof-root-store/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This pallet provides mechanisms for tracking and updating data related to the state root of an
//! external chain.
//!
//! The `T::Value` type represents state root-related data—for example, a `state_root` or
//! an entire `HeadData` structure.
//! The `T::Key` type serves as the identifier in the map where we store `T::Value`.
//! For example, it could be a `block_hash` or `block_number`.
//!
//! Example use cases:
//! 1. Store a `block_hash` → `state_root` mapping.
//! 2. Store a `block_number` → `HeadData` mapping.
//!
//! Root data is stored in a ring buffer, respecting the `T::RootsToKeep` limit.
//! When the limit is reached, the oldest entries are removed.
//!
//! There are two approaches for storing data:
//! 1. Send root data between chains using the dedicated extrinsic `fn note_new_roots(...)`.
//! 2. Implement an adapter (e.g., using the `OnSystemEvent` callback) that triggers
//! `pallet_bridge_proof_root_store::Pallet::<T, I>::do_note_new_roots(...)`.

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

extern crate alloc;

pub use pallet::*;
pub mod weights;
pub use weights::WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking {
// TODO: FAIL-CI
}
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

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

/// Configuration trait for the pallet.
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

/// The origin allowed submitting head updates.
type SubmitOrigin: EnsureOrigin<Self::RuntimeOrigin>;

/// The key type used to identify stored values of type `T::Value`.
type Key: Parameter + MaxEncodedLen;

/// The type of the root value.
type Value: Parameter + MaxEncodedLen;

/// Maximum number of roots to retain in storage.
/// This setting prevents unbounded growth of the on-chain state.
#[pallet::constant]
type RootsToKeep: Get<u32>;
}

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

/// Current ring buffer position.
#[pallet::storage]
pub(super) type RootKeysPointer<T: Config<I>, I: 'static = ()> =
StorageValue<_, u32, ValueQuery>;

/// A ring buffer of imported keys. Ordered by the insertion time.
#[pallet::storage]
pub(super) type RootKeys<T: Config<I>, I: 'static = ()> = StorageMap<
Hasher = Identity,
Key = u32,
Value = T::Key,
QueryKind = OptionQuery,
OnEmpty = GetDefault,
MaxValues = MaybeRootsToKeep<T, I>,
>;

/// Storage for root-related k-v data, bounded by `RootKeysPointer+RootKeys` ring buffer.
#[pallet::storage]
pub type Roots<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::Key, T::Value, OptionQuery>;

/// Adapter for using `Config::RootsToKeep` as `MaxValues` bound in our storage maps.
pub struct MaybeRootsToKeep<T, I>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeRootsToKeep<T, I> {
fn get() -> Option<u32> {
Some(T::RootsToKeep::get())
}
}

#[pallet::call(weight(<T as Config<I>>::WeightInfo))]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Records a new root data.
#[pallet::call_index(0)]
pub fn note_new_roots(
origin: OriginFor<T>,
roots: BoundedVec<(T::Key, T::Value), T::RootsToKeep>,
) -> DispatchResult {
let _ = T::SubmitOrigin::ensure_origin(origin);
Self::do_note_new_roots(roots);
Ok(())
}
}

#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
#[cfg(feature = "try-runtime")]
fn try_state(
_n: BlockNumberFor<T>,
) -> Result<(), frame_support::sp_runtime::TryRuntimeError> {
Self::do_try_state()
}
}

#[cfg(any(feature = "try-runtime", test))]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Ensure the correctness of the state of this pallet.
pub fn do_try_state() -> Result<(), frame_support::sp_runtime::TryRuntimeError> {
// Check that the ring buffer is aligned with `Roots`.
ensure!(
RootKeys::<T, I>::iter_values().count() == Roots::<T, I>::iter_keys().count(),
"`RootIndex` contains different keys than `Roots`"
);
for key in RootKeys::<T, I>::iter_values() {
ensure!(
Roots::<T, I>::get(key).is_some(),
"`Roots` does not contain the key from `RootKeys`!"
);
}

Ok(())
}
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Stores root values.
pub fn do_note_new_roots(roots: BoundedVec<(T::Key, T::Value), T::RootsToKeep>) {
// Insert `roots` to the `Roots` bounded by `RootKeysPointer+RootKeys`.
for (key, value) in roots {
let index = <RootKeysPointer<T, I>>::get();
let pruning = <RootKeys<T, I>>::try_get(index);

<Roots<T, I>>::insert(&key, value);
<RootKeys<T, I>>::insert(index, key);

// Update ring buffer pointer and remove old root.
<RootKeysPointer<T, I>>::put((index + 1) % T::RootsToKeep::get());
if let Ok(key_to_prune) = pruning {
// log::debug!(target: LOG_TARGET, "Pruning old header: {:?}.", key_to_prune);
<Roots<T, I>>::remove(key_to_prune);
}
}
}

/// Returns the stored value for the given key.
pub fn get_root(key: &T::Key) -> Option<T::Value> {
Roots::<T, I>::get(key)
}

/// Returns the stored root keys.
#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
pub fn get_root_keys() -> Vec<T::Key> {
Roots::<T, I>::iter_keys().collect()
}
}
}
56 changes: 56 additions & 0 deletions bridges/modules/proof-root-store/src/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![cfg(test)]

use crate as pallet_bridge_proof_root_store;
use frame_support::{
derive_impl,
sp_runtime::{traits::ConstU32, BuildStorage},
};
use frame_system::EnsureRoot;

type Block = frame_system::mocking::MockBlock<TestRuntime>;

frame_support::construct_runtime! {
pub enum TestRuntime {
System: frame_system,
ProofRootStore: pallet_bridge_proof_root_store,
}
}

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
}

impl pallet_bridge_proof_root_store::Config for TestRuntime {
type WeightInfo = ();
type SubmitOrigin = EnsureRoot<u64>;
type Key = u8;
type Value = u8;
type RootsToKeep = ConstU32<4>;
}

/// Return test externalities to use in tests.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
sp_io::TestExternalities::new(t)
}

/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(test)
}
Loading
Loading