Skip to content

Commit 4c1551e

Browse files
committed
BACKPORT/REUSE some bridge stuff from different PRs:
#8326 #8325 Add proof root sync (`pallet-bridge-proof-root-sync`) as an ring buffer with on_idle callback Add `OnNewHead` to `pallet-bridge-parachains`
1 parent e7f6d68 commit 4c1551e

13 files changed

Lines changed: 465 additions & 5 deletions

File tree

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ members = [
1616
"bridges/modules/grandpa",
1717
"bridges/modules/messages",
1818
"bridges/modules/parachains",
19+
"bridges/modules/proof-root-sync",
1920
"bridges/modules/relayers",
2021
"bridges/modules/xcm-bridge-hub",
2122
"bridges/modules/xcm-bridge-hub-router",
@@ -943,6 +944,7 @@ pallet-bounties = { path = "substrate/frame/bounties", default-features = false
943944
pallet-bridge-grandpa = { path = "bridges/modules/grandpa", default-features = false }
944945
pallet-bridge-messages = { path = "bridges/modules/messages", default-features = false }
945946
pallet-bridge-parachains = { path = "bridges/modules/parachains", default-features = false }
947+
pallet-bridge-proof-root-sync = { path = "bridges/modules/proof-root-sync", default-features = false }
946948
pallet-bridge-relayers = { path = "bridges/modules/relayers", default-features = false }
947949
pallet-broker = { path = "substrate/frame/broker", default-features = false }
948950
pallet-child-bounties = { path = "substrate/frame/child-bounties", default-features = false }

bridges/modules/parachains/src/lib.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use bp_parachains::{
3232
ParaInfo, ParaStoredHeaderData, RelayBlockHash, RelayBlockHasher, RelayBlockNumber,
3333
SubmitParachainHeadsInfo,
3434
};
35-
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
35+
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
3636
use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain};
3737
use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound};
3838
use pallet_bridge_grandpa::SubmitFinalityProofHelper;
@@ -76,7 +76,7 @@ struct UpdateParachainHeadArtifacts {
7676
pub mod pallet {
7777
use super::*;
7878
use bp_parachains::{
79-
BestParaHeadHash, ImportedParaHeadsKeyProvider, ParaStoredHeaderDataBuilder,
79+
BestParaHeadHash, ImportedParaHeadsKeyProvider, OnNewHead, ParaStoredHeaderDataBuilder,
8080
ParasInfoKeyProvider,
8181
};
8282
use bp_runtime::{
@@ -252,6 +252,9 @@ pub mod pallet {
252252
/// that exceeds this bound.
253253
#[pallet::constant]
254254
type MaxParaHeadDataSize: Get<u32>;
255+
256+
/// Runtime hook for when a parachain head is updated.
257+
type OnNewHead: OnNewHead;
255258
}
256259

257260
/// Optional pallet owner.
@@ -538,6 +541,7 @@ pub mod pallet {
538541
HeaderId(relay_block_number, relay_block_hash),
539542
parachain_head_data,
540543
parachain_head_hash,
544+
parachain_head,
541545
)?;
542546

543547
if is_free {
@@ -638,6 +642,7 @@ pub mod pallet {
638642
new_at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
639643
new_head_data: ParaStoredHeaderData,
640644
new_head_hash: ParaHash,
645+
new_head: ParaHead,
641646
) -> Result<UpdateParachainHeadArtifacts, ()> {
642647
// check if head has been already updated at better relay chain block. Without this
643648
// check, we may import heads in random order
@@ -699,7 +704,7 @@ pub mod pallet {
699704
next_imported_hash_position,
700705
new_head_hash,
701706
);
702-
ImportedParaHeads::<T, I>::insert(parachain, new_head_hash, updated_head_data);
707+
ImportedParaHeads::<T, I>::insert(parachain, new_head_hash, &updated_head_data);
703708
log::trace!(
704709
target: LOG_TARGET,
705710
"Updated head of parachain {:?} to {} at relay block {}",
@@ -708,6 +713,9 @@ pub mod pallet {
708713
new_at_relay_block.0,
709714
);
710715

716+
// trigger callback
717+
T::OnNewHead::on_new_head(parachain, &new_head);
718+
711719
// remove old head
712720
let prune_happened = head_hash_to_prune.is_ok();
713721
if let Ok(head_hash_to_prune) = head_hash_to_prune {

bridges/modules/parachains/src/mock.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ impl pallet_bridge_parachains::Config for TestRuntime {
215215
type ParaStoredHeaderDataBuilder = (Parachain1, Parachain2, Parachain3, BigParachain);
216216
type HeadsToKeep = HeadsToKeep;
217217
type MaxParaHeadDataSize = ConstU32<MAXIMAL_PARACHAIN_HEAD_DATA_SIZE>;
218+
type OnNewHead = ();
218219
}
219220

220221
#[cfg(feature = "runtime-benchmarks")]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
[package]
2+
name = "pallet-bridge-proof-root-sync"
3+
version = "0.1.0"
4+
description = "Module that allows bridged relay chains to exchange information on their parachains' heads."
5+
authors.workspace = true
6+
edition.workspace = true
7+
homepage.workspace = true
8+
license.workspace = true
9+
repository.workspace = true
10+
11+
[lints]
12+
workspace = true
13+
14+
[dependencies]
15+
bp-parachains = { workspace = true }
16+
bp-polkadot-core = { workspace = true }
17+
bp-runtime = { workspace = true }
18+
codec = { features = ["derive"], workspace = true }
19+
frame-benchmarking = { optional = true, workspace = true }
20+
frame-support = { workspace = true }
21+
frame-system = { workspace = true }
22+
impl-trait-for-tuples = { workspace = true }
23+
scale-info = { features = ["derive"], workspace = true }
24+
tracing = { workspace = true }
25+
26+
[dev-dependencies]
27+
sp-io = { workspace = true }
28+
29+
[features]
30+
default = ["std"]
31+
runtime-benchmarks = [
32+
"frame-benchmarking",
33+
"frame-benchmarking/runtime-benchmarks",
34+
"frame-support/runtime-benchmarks",
35+
"frame-system/runtime-benchmarks",
36+
]
37+
std = [
38+
"bp-parachains/std",
39+
"bp-polkadot-core/std",
40+
"bp-runtime/std",
41+
"codec/std",
42+
"frame-benchmarking?/std",
43+
"frame-support/std",
44+
"frame-system/std",
45+
"scale-info/std",
46+
"tracing/std",
47+
]
48+
try-runtime = [
49+
"frame-support/try-runtime",
50+
"frame-system/try-runtime",
51+
]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (C) Parity Technologies (UK) Ltd.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
//! Various predefined implementations for the pallet.
17+
18+
use crate::{Config, Pallet};
19+
use bp_parachains::OnNewHead;
20+
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaId};
21+
use bp_runtime::Parachain;
22+
use frame_support::{traits::Get, weights::Weight};
23+
24+
/// An adapter `OnNewHead` implementation that listens for parachain head updates and schedules them
25+
/// for syncing.
26+
pub struct SyncParaHeadersFor<T, I, C>(core::marker::PhantomData<(T, I, C)>);
27+
impl<T: Config<I, Key = ParaId, Value = ParaHead>, I: 'static, C: Parachain<Hash = ParaHash>>
28+
OnNewHead for SyncParaHeadersFor<T, I, C>
29+
{
30+
fn on_new_head(id: ParaId, head: &ParaHead) -> Weight {
31+
// Filter by para ID.
32+
if C::PARACHAIN_ID != id.0 {
33+
return Weight::zero();
34+
}
35+
36+
// Schedule for syncing.
37+
Pallet::<T, I>::schedule_for_sync(id, head.clone());
38+
39+
// Schedule does one read and one write.
40+
T::DbWeight::get().reads_writes(1, 1)
41+
}
42+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright (C) Parity Technologies (UK) Ltd.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
//! A pallet for scheduling and syncing key-value pairs (roots) with arbitrary destinations.
17+
//!
18+
//! The pallet provides functionality to:
19+
//! - Schedule roots for syncing using `schedule_for_sync`
20+
//! - Automatically process scheduled roots during `on_idle` hooks
21+
//!
22+
//! The actual sending/syncing of roots is implemented by the `OnSend` trait, which can be
23+
//! customized for specific use cases like cross-chain communication.
24+
//!
25+
//! Basically, this is a simple `on_idle` hook that can schedule data with a ring buffer and send
26+
//! data.
27+
28+
#![cfg_attr(not(feature = "std"), no_std)]
29+
30+
extern crate alloc;
31+
32+
use alloc::{collections::VecDeque, vec::Vec};
33+
use frame_support::pallet_prelude::Weight;
34+
35+
pub mod impls;
36+
37+
#[cfg(test)]
38+
mod mock;
39+
#[cfg(test)]
40+
mod tests;
41+
42+
pub use pallet::*;
43+
44+
const LOG_TARGET: &str = "runtime::bridge-proof-root-sync";
45+
46+
/// A trait for sending/syncing roots, for example, to other chains.
47+
pub trait OnSend<Key, Value> {
48+
/// Process a list of roots (key-value pairs) for sending.
49+
///
50+
/// # Arguments
51+
///
52+
/// * `roots` - A vector of roots where each root is a tuple of (key, value). Roots are ordered
53+
/// from the oldest (index 0) to the newest (last index).
54+
fn on_send(roots: &Vec<(Key, Value)>);
55+
56+
/// Returns the weight consumed by `on_send`.
57+
fn on_send_weight() -> Weight;
58+
}
59+
60+
#[impl_trait_for_tuples::impl_for_tuples(8)]
61+
impl<Key, Value> OnSend<Key, Value> for Tuple {
62+
fn on_send(roots: &Vec<(Key, Value)>) {
63+
for_tuples!( #( Tuple::on_send(roots);) * );
64+
}
65+
66+
fn on_send_weight() -> Weight {
67+
let mut weight: Weight = Default::default();
68+
for_tuples!( #( weight.saturating_accrue(Tuple::on_send_weight()); )* );
69+
weight
70+
}
71+
}
72+
73+
#[frame_support::pallet]
74+
pub mod pallet {
75+
use super::*;
76+
use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion, weights::WeightMeter};
77+
use frame_system::pallet_prelude::*;
78+
79+
#[pallet::pallet]
80+
pub struct Pallet<T, I = ()>(_);
81+
82+
/// The pallet configuration trait.
83+
#[pallet::config]
84+
pub trait Config<I: 'static = ()>: frame_system::Config {
85+
/// The key type used to identify stored values of type `T::Value`.
86+
type Key: Parameter;
87+
88+
/// The type of the root value.
89+
type Value: Parameter;
90+
91+
/// Maximum number of roots to retain in `RootsToSend` storage.
92+
/// This setting prevents unbounded growth of the on-chain state.
93+
/// If we hit this number, we start removing the oldest data from `RootsToSend`.
94+
#[pallet::constant]
95+
type RootsToKeep: Get<u32>;
96+
97+
/// Maximum number of roots to drain and send with `T::OnSend`.
98+
#[pallet::constant]
99+
type MaxRootsToSend: Get<u32>;
100+
101+
/// Means for sending/syncing roots.
102+
type OnSend: OnSend<Self::Key, Self::Value>;
103+
}
104+
105+
/// A ring-buffer storage of roots (key-value pairs) that need to be sent/synced to other
106+
/// chains. When the buffer reaches its capacity limit defined by `T::RootsToKeep`, the oldest
107+
/// elements are removed. The elements are drained and processed in order by `T::OnSend` during
108+
/// `on_idle` up to `T::MaxRootsToSend` elements at a time.
109+
#[pallet::storage]
110+
#[pallet::unbounded]
111+
pub type RootsToSend<T: Config<I>, I: 'static = ()> =
112+
StorageValue<_, VecDeque<(T::Key, T::Value)>, ValueQuery>;
113+
114+
#[pallet::hooks]
115+
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
116+
fn on_idle(_n: BlockNumberFor<T>, limit: Weight) -> Weight {
117+
let mut meter = WeightMeter::with_limit(limit);
118+
if meter.try_consume(Self::on_idle_weight()).is_err() {
119+
tracing::debug!(
120+
target: LOG_TARGET,
121+
?limit,
122+
on_idle_weight = ?Self::on_idle_weight(),
123+
"Not enough weight for on_idle.",
124+
);
125+
return meter.consumed();
126+
}
127+
128+
// Send roots.
129+
RootsToSend::<T, I>::mutate(|roots| {
130+
let range_for_send =
131+
0..core::cmp::min(T::MaxRootsToSend::get().saturated_into(), roots.len());
132+
T::OnSend::on_send(&roots.drain(range_for_send).collect::<Vec<_>>())
133+
});
134+
135+
meter.consumed()
136+
}
137+
}
138+
139+
impl<T: Config<I>, I: 'static> Pallet<T, I> {
140+
/// The worst-case weight of [`Self::on_idle`].
141+
fn on_idle_weight() -> Weight {
142+
T::DbWeight::get()
143+
.reads_writes(1, 1)
144+
.saturating_add(T::OnSend::on_send_weight())
145+
}
146+
147+
/// Schedule new data to be synced by `T::OnSend` means.
148+
///
149+
/// The roots are stored in a ring buffer with limited capacity as defined by
150+
/// `T::RootsToKeep`. When the buffer reaches its capacity limit, the oldest elements are
151+
/// removed. The elements will be drained and processed in order by `T::OnSend` during
152+
/// `on_idle` up to `T::MaxRootsToSend` elements at a time.
153+
pub fn schedule_for_sync(key: T::Key, value: T::Value) {
154+
RootsToSend::<T, I>::mutate(|roots| {
155+
// Add to schedules.
156+
roots.push_back((key, value));
157+
158+
// Remove from the front up to the `T::RootsToKeep` limit.
159+
let max = T::RootsToKeep::get();
160+
while roots.len() > (max as usize) {
161+
let _ = roots.pop_front();
162+
}
163+
});
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)