Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 parachain/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 parachain/pallets/ethereum-beacon-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = f
snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false }

[dev-dependencies]
rand = "0.8.5"
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
snowbridge-testutils = { path = "../../primitives/testutils" }
serde_json = "1.0.96"
Expand Down
111 changes: 86 additions & 25 deletions parachain/pallets/ethereum-beacon-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ mod tests_minimal;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

pub use weights::WeightInfo;
mod ringbuffer;

use crate::merkleization::get_sync_committee_bits;
use crate::{
merkleization::get_sync_committee_bits,
ringbuffer::{RingBufferMap, RingBufferMapImpl},
};
use frame_support::{dispatch::DispatchResult, log, traits::UnixTime, transactional};
use frame_system::ensure_signed;
use snowbridge_beacon_primitives::{
Expand All @@ -32,6 +35,7 @@ use snowbridge_core::{Message, Verifier};
use sp_core::H256;
use sp_io::hashing::sha2_256;
use sp_std::prelude::*;
pub use weights::WeightInfo;

use frame_support::{traits::Get, BoundedVec};

Expand Down Expand Up @@ -99,6 +103,12 @@ pub mod pallet {
type MaxFinalizedHeaderSlotArray: Get<u32>;
#[pallet::constant]
type ForkVersions: Get<ForkVersions>;
/// Maximum execution headers are stored
#[pallet::constant]
type ExecutionHeadersPruneThreshold: Get<u64>;
/// Maximum sync committees to be stored
#[pallet::constant]
type SyncCommitteePruneThreshold: Get<u64>;
type WeightInfo: WeightInfo;
type WeakSubjectivityPeriodSeconds: Get<u64>;
}
Expand Down Expand Up @@ -164,8 +174,11 @@ pub mod pallet {
StorageMap<_, Identity, H256, BeaconHeader, OptionQuery>;

#[pallet::storage]
pub(super) type FinalizedBeaconHeaderSlots<T: Config> =
StorageValue<_, BoundedVec<u64, T::MaxFinalizedHeaderSlotArray>, ValueQuery>;
pub(super) type FinalizedBeaconHeaderStates<T: Config> = StorageValue<
_,
BoundedVec<FinalizedHeaderState, T::MaxFinalizedHeaderSlotArray>,
ValueQuery,
>;

#[pallet::storage]
pub(super) type FinalizedBeaconHeadersBlockRoot<T: Config> =
Expand All @@ -175,12 +188,53 @@ pub mod pallet {
pub(super) type ExecutionHeaders<T: Config> =
StorageMap<_, Identity, H256, ExecutionHeader, OptionQuery>;

/// Execution headers ring buffer map implementation

/// Index storage for execution header ring buffer map
#[pallet::storage]
pub(crate) type ExecutionHeaderBufferIndex<T: Config> = StorageValue<_, u64, ValueQuery>;
Comment thread
yrong marked this conversation as resolved.
Outdated

/// Intermediate storage for execution header mapping
#[pallet::storage]
pub(crate) type ExecutionHeaderMapping<T: Config> =
StorageMap<_, Identity, u64, H256, ValueQuery>;

/// Ring buffer Map for Execution header
pub(crate) type ExecutionHeaderRingBufferMap<T> = RingBufferMapImpl<
Comment thread
vgeddes marked this conversation as resolved.
Outdated
Comment thread
yrong marked this conversation as resolved.
Outdated
u64,
<T as Config>::ExecutionHeadersPruneThreshold,
ExecutionHeaderBufferIndex<T>,
ExecutionHeaderMapping<T>,
ExecutionHeaders<T>,
OptionQuery,
>;

/// Current sync committee corresponding to the active header.
/// TODO prune older sync committees than xxx
#[pallet::storage]
pub(super) type SyncCommittees<T: Config> =
StorageMap<_, Identity, u64, SyncCommitteeOf<T>, ValueQuery>;

/// Sync committee ring buffer map implementation

/// Index storage for sync committee ring buffer
#[pallet::storage]
pub(crate) type SyncCommitteesBufferIndex<T: Config> = StorageValue<_, u64, ValueQuery>;
Comment thread
yrong marked this conversation as resolved.
Outdated

/// Intermediate storage for sync committee mapping
#[pallet::storage]
pub(crate) type SyncCommitteesMapping<T: Config> =
StorageMap<_, Identity, u64, u64, ValueQuery>;

/// Ring buffer Map for Sync committee
pub(crate) type SyncCommitteesRingBufferMap<T> = RingBufferMapImpl<
Comment thread
vgeddes marked this conversation as resolved.
Outdated
Comment thread
yrong marked this conversation as resolved.
Outdated
u64,
<T as Config>::SyncCommitteePruneThreshold,
SyncCommitteesBufferIndex<T>,
SyncCommitteesMapping<T>,
SyncCommittees<T>,
ValueQuery,
>;

#[pallet::storage]
pub(super) type ValidatorsRoot<T: Config> = StorageValue<_, H256, ValueQuery>;

Expand Down Expand Up @@ -384,7 +438,7 @@ pub mod pallet {
};

<FinalizedBeaconHeaders<T>>::insert(block_root, initial_sync.header);
Self::add_finalized_header_slot(slot)?;
Self::add_finalized_header_state(last_finalized_header)?;
<LatestFinalizedHeaderState<T>>::set(last_finalized_header);

Ok(())
Expand Down Expand Up @@ -433,12 +487,12 @@ pub mod pallet {
signature_slot_period
);
ensure!(
<SyncCommittees<T>>::contains_key(current_period),
<SyncCommitteesRingBufferMap<T>>::contains_key(current_period),
Error::<T>::SyncCommitteeMissing
);
let next_period = current_period + 1;
ensure!(
!<SyncCommittees<T>>::contains_key(next_period),
!<SyncCommitteesRingBufferMap<T>>::contains_key(next_period),
Error::<T>::InvalidSyncCommitteePeriodUpdateWithDuplication
);
ensure!(
Expand Down Expand Up @@ -864,8 +918,8 @@ pub mod pallet {
Ok(())
}

fn store_sync_committee(period: u64, sync_committee: SyncCommitteeOf<T>) {
<SyncCommittees<T>>::insert(period, sync_committee);
pub(crate) fn store_sync_committee(period: u64, sync_committee: SyncCommitteeOf<T>) {
<SyncCommitteesRingBufferMap<T>>::insert(period, sync_committee);

log::trace!(
target: "ethereum-beacon-client",
Expand All @@ -881,8 +935,15 @@ pub mod pallet {
fn store_finalized_header(block_root: Root, header: BeaconHeader) -> DispatchResult {
let slot = header.slot;

let finalized_header = FinalizedHeaderState {
beacon_block_root: block_root,
beacon_slot: slot,
import_time: T::TimeProvider::now().as_secs(),
};

<FinalizedBeaconHeaders<T>>::insert(block_root, header);
Self::add_finalized_header_slot(slot)?;
LatestFinalizedHeaderState::<T>::set(finalized_header);
Self::add_finalized_header_state(finalized_header)?;

log::info!(
target: "ethereum-beacon-client",
Expand All @@ -891,38 +952,38 @@ pub mod pallet {
slot
);

LatestFinalizedHeaderState::<T>::mutate(|s| {
s.import_time = T::TimeProvider::now().as_secs();
s.beacon_block_root = block_root;
s.beacon_slot = slot;
});

Self::deposit_event(Event::BeaconHeaderImported { block_hash: block_root, slot });

Ok(())
}

fn add_finalized_header_slot(slot: u64) -> DispatchResult {
<FinalizedBeaconHeaderSlots<T>>::try_mutate(|b_vec| {
pub(super) fn add_finalized_header_state(
finalized_header_state: FinalizedHeaderState,
) -> DispatchResult {
<FinalizedBeaconHeaderStates<T>>::try_mutate(|b_vec| {
if b_vec.len() as u32 == T::MaxFinalizedHeaderSlotArray::get() {
b_vec.remove(0);
let oldest = b_vec.remove(0);
// Removing corresponding finalized header data of popped slot
// as that data will not be used by relayer anyway.
<FinalizedBeaconHeadersBlockRoot<T>>::remove(oldest.beacon_block_root);
<FinalizedBeaconHeaders<T>>::remove(oldest.beacon_block_root);
}
b_vec.try_push(slot)
b_vec.try_push(finalized_header_state)
})
.map_err(|_| <Error<T>>::FinalizedBeaconHeaderSlotsExceeded)?;

Ok(())
}

fn store_execution_header(
pub(crate) fn store_execution_header(
block_hash: H256,
header: ExecutionHeader,
beacon_slot: u64,
beacon_block_root: H256,
) {
let block_number = header.block_number;

<ExecutionHeaders<T>>::insert(block_hash, header);
<ExecutionHeaderRingBufferMap<T>>::insert(block_hash, header);

log::trace!(
target: "ethereum-beacon-client",
Expand Down Expand Up @@ -1044,7 +1105,7 @@ pub mod pallet {
pub(super) fn get_sync_committee_for_period(
period: u64,
) -> Result<SyncCommitteeOf<T>, DispatchError> {
let sync_committee = <SyncCommittees<T>>::get(period);
let sync_committee = <SyncCommitteesRingBufferMap<T>>::get(period);

if sync_committee.pubkeys.len() == 0 {
log::error!(target: "ethereum-beacon-client", "💫 Sync committee for period {} missing", period);
Expand Down Expand Up @@ -1128,7 +1189,7 @@ pub mod pallet {
message.proof.block_hash,
);

let stored_header = <ExecutionHeaders<T>>::get(message.proof.block_hash)
let stored_header = <ExecutionHeaderRingBufferMap<T>>::get(message.proof.block_hash)
.ok_or(Error::<T>::MissingHeader)?;

let receipt = match Self::verify_receipt_inclusion(stored_header, &message.proof) {
Expand Down
10 changes: 9 additions & 1 deletion parachain/pallets/ethereum-beacon-client/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ pub mod mock_minimal {
pub const MaxPublicKeySize: u32 = config::PUBKEY_SIZE as u32;
pub const MaxSignatureSize: u32 = config::SIGNATURE_SIZE as u32;
pub const MaxSlotsPerHistoricalRoot: u64 = 64;
pub const MaxFinalizedHeaderSlotArray: u32 = 1000;
pub const MaxFinalizedHeaderSlotArray: u32 = 12;
pub const SyncCommitteePruneThreshold: u64 = 4;
pub const ExecutionHeadersPruneThreshold: u64 = 10;
pub const WeakSubjectivityPeriodSeconds: u32 = 97200;
pub const ChainForkVersions: ForkVersions = ForkVersions{
genesis: Fork {
Expand Down Expand Up @@ -112,6 +114,8 @@ pub mod mock_minimal {
type MaxSlotsPerHistoricalRoot = MaxSlotsPerHistoricalRoot;
type MaxFinalizedHeaderSlotArray = MaxFinalizedHeaderSlotArray;
type ForkVersions = ChainForkVersions;
type SyncCommitteePruneThreshold = SyncCommitteePruneThreshold;
type ExecutionHeadersPruneThreshold = ExecutionHeadersPruneThreshold;
type WeakSubjectivityPeriodSeconds = WeakSubjectivityPeriodSeconds;
type WeightInfo = ();
}
Expand Down Expand Up @@ -203,6 +207,8 @@ pub mod mock_mainnet {
epoch: 162304,
},
};
pub const SyncCommitteePruneThreshold: u64 = 4;
pub const ExecutionHeadersPruneThreshold: u64 = 10;
}

impl ethereum_beacon_client::Config for Test {
Expand All @@ -218,6 +224,8 @@ pub mod mock_mainnet {
type MaxSlotsPerHistoricalRoot = MaxSlotsPerHistoricalRoot;
type MaxFinalizedHeaderSlotArray = MaxFinalizedHeaderSlotArray;
type ForkVersions = ChainForkVersions;
type SyncCommitteePruneThreshold = SyncCommitteePruneThreshold;
type ExecutionHeadersPruneThreshold = ExecutionHeadersPruneThreshold;
type WeakSubjectivityPeriodSeconds = WeakSubjectivityPeriodSeconds;
type WeightInfo = ();
}
Expand Down
74 changes: 74 additions & 0 deletions parachain/pallets/ethereum-beacon-client/src/ringbuffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use codec::FullCodec;
use core::{cmp::Ord, marker::PhantomData, ops::Add};
use frame_support::storage::{types::QueryKindTrait, StorageMap, StorageValue};
use sp_core::{Get, GetDefault};
use sp_runtime::traits::{One, Zero};

/// Trait object presenting the ringbuffer interface.
pub trait RingBufferMap<Key, Value, QueryKind>
where
Key: FullCodec,
Value: FullCodec,
QueryKind: QueryKindTrait<Value, GetDefault>,
{
/// Insert a map entry.
fn insert(k: Key, v: Value);

/// Check if map contains a key
fn contains_key(k: Key) -> bool;

/// Get the value of the key
fn get(k: Key) -> QueryKind::Query;
}

pub struct RingBufferMapImpl<Index, B, CurrentIndex, Intermediate, M, QueryKind>(
PhantomData<(Index, B, CurrentIndex, Intermediate, M, QueryKind)>,
);

/// Ringbuffer implementation based on `RingBufferTransient`
impl<Key, Value, Index, B, CurrentIndex, Intermediate, M, QueryKind>
RingBufferMap<Key, Value, QueryKind>
for RingBufferMapImpl<Index, B, CurrentIndex, Intermediate, M, QueryKind>
where
Key: FullCodec + Clone,
Value: FullCodec,
Index: Ord + One + Zero + Add<Output = Index> + Copy + FullCodec + Eq,
B: Get<Index>,
CurrentIndex: StorageValue<Index, Query = Index>,
Intermediate: StorageMap<Index, Key, Query = Key>,
M: StorageMap<Key, Value, Query = QueryKind::Query>,
QueryKind: QueryKindTrait<Value, GetDefault>,
{
/// Insert a map entry.
fn insert(k: Key, v: Value) {
let bound = B::get();
let mut current_index = CurrentIndex::get();

// Adding one here as bound denotes number of items but our index starts with zero.
if (current_index + Index::one()) >= bound {
current_index = Index::zero();
} else {
current_index = current_index + Index::one();
}

// Deleting earlier entry if it exists
if Intermediate::contains_key(current_index) {
let older_key = Intermediate::get(current_index);
M::remove(older_key);
}

Intermediate::insert(current_index, k.clone());
CurrentIndex::set(current_index);
M::insert(k, v);
}

/// Check if map contains a key
fn contains_key(k: Key) -> bool {
M::contains_key(k)
}

/// Get the value associated with key
fn get(k: Key) -> M::Query {
M::get(k)
}
}
Loading