Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(checkpoints): Store slot directly with a checkpoint #135

Merged
merged 8 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion gear-programs/checkpoint-light-client/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use io::{
},
meta,
sync_update::Error as SyncCommitteeUpdateError,
BeaconBlockHeader, Handle, HandleResult, Init, SyncCommitteeKeys, SyncCommitteeUpdate, G1, G2,
BeaconBlockHeader, Handle, HandleResult, Init, Slot, SyncCommitteeKeys, SyncCommitteeUpdate,
G1, G2,
};
use primitive_types::H256;
use state::{Checkpoints, ReplayBackState, State};
Expand Down
170 changes: 27 additions & 143 deletions gear-programs/checkpoint-light-client/src/wasm/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::*;
use circular_buffer::CircularBuffer;
use io::{
ethereum_common::{network::Network, Hash256, SLOTS_PER_EPOCH},
BeaconBlockHeader, CheckpointError, Slot, SyncCommitteeKeys,
BeaconBlockHeader, CheckpointError, SyncCommitteeKeys,
};

pub struct State<const N: usize> {
Expand All @@ -22,163 +22,47 @@ pub struct ReplayBackState {
}

#[derive(Debug, Clone)]
pub struct Checkpoints<const N: usize> {
checkpoints: Box<CircularBuffer<N, Hash256>>,
slots: Vec<(usize, Slot)>,
}
pub struct Checkpoints<const N: usize>(Box<CircularBuffer<N, (Slot, Hash256)>>);
gshep marked this conversation as resolved.
Show resolved Hide resolved

impl<const N: usize> Checkpoints<N> {
pub fn new() -> Self {
Self {
checkpoints: CircularBuffer::boxed(),
slots: Vec::with_capacity(N / 2),
}
Self(CircularBuffer::boxed())
}

pub fn push(&mut self, slot: Slot, checkpoint: Hash256) {
let len = self.checkpoints.len();
let overwrite = len >= self.checkpoints.capacity();
let slot_last = self.last().map(|(slot, _checkpoint)| slot);

self.checkpoints.push_back(checkpoint);

if overwrite {
let maybe_index_second = self.slots.get(1).map(|(index, _slot)| *index);
match (self.slots.get_mut(0), maybe_index_second) {
(Some((index_first, slot_first)), Some(index_second)) => {
if *index_first == 0 && index_second == 1 {
self.slots.remove(0);
self.slots[0].0 -= 1;
} else {
*slot_first += SLOTS_PER_EPOCH;
}
}

(Some((_index_first, slot_first)), None) => *slot_first += SLOTS_PER_EPOCH,

_ => unreachable!(),
}

// adjust indexes. We skip the first item since it always points to the first checkpoint.
for (index, _) in self.slots.iter_mut().skip(1) {
*index -= 1;
}
}

match self.slots.last() {
None => (),

Some((_, slot_previous))
if slot % SLOTS_PER_EPOCH != 0
|| slot_last
.map(|slot_last| slot_last + SLOTS_PER_EPOCH < slot)
.unwrap_or(false)
|| slot_previous % SLOTS_PER_EPOCH != 0 => {}

_ => return,
}

self.slots
.push((if overwrite { len - 1 } else { len }, slot));
self.0.push_back((slot, checkpoint))
}

pub fn checkpoints(&self) -> Vec<(Slot, Hash256)> {
let mut result = Vec::with_capacity(self.checkpoints.len());
for indexes in self.slots.windows(2) {
let (index_first, slot_first) = indexes[0];
let (index_second, _slot_second) = indexes[1];
if index_first + 1 == index_second {
result.push((slot_first, self.checkpoints[index_first]));
} else {
result.extend(
self.checkpoints
.iter()
.skip(index_first)
.take(index_second - index_first)
.enumerate()
.map(|(slot, checkpoint)| {
(slot_first + SLOTS_PER_EPOCH * slot as u64, *checkpoint)
}),
);
}
}

if let Some((index_first, slot_first)) = self.slots.last() {
result.extend(self.checkpoints.iter().skip(*index_first).enumerate().map(
|(slot, checkpoint)| (*slot_first + SLOTS_PER_EPOCH * slot as u64, *checkpoint),
));
}

result
self.0.to_vec()
}

pub fn checkpoint(&self, slot: Slot) -> Result<(Slot, Hash256), CheckpointError> {
let Some((index_last, slot_last)) = self.slots.last() else {
return Err(CheckpointError::NotPresent);
let search = |slice: &[(Slot, Hash256)]| match slice
.binary_search_by(|(slot_current, _checkpoint)| slot_current.cmp(&slot))
{
Ok(index) => Ok(slice[index]),
Err(index_next) => match slice.get(index_next) {
Some(result) => Ok(*result),
None => Err(CheckpointError::NotPresent),
},
};

match self
.slots
.binary_search_by(|(_index, slot_checkpoint)| slot_checkpoint.cmp(&slot))
{
Ok(index) => Ok((slot, self.checkpoints[self.slots[index].0])),

Err(0) => Err(CheckpointError::OutDated),

Err(index) => {
let (index_previous, slot_previous) = self.slots[index - 1];
let maybe_next = self.slots.get(index);

let count = maybe_next
.map(|(index_next, _slot)| *index_next)
.unwrap_or(self.checkpoints.len())
- index_previous;
for i in 1..count {
let slot_next = slot_previous + i as u64 * SLOTS_PER_EPOCH;
if slot <= slot_next {
return Ok((slot_next, self.checkpoints[index_previous + i]));
}
}

match maybe_next {
None => Err(CheckpointError::NotPresent),
Some((index_next, slot_next)) => {
Ok((*slot_next, self.checkpoints[*index_next]))
}
}
}
}
let (left, right) = self.0.as_slices();

search(left).or(search(right))
}

pub fn checkpoint_by_index(&self, index: usize) -> Option<(Slot, Hash256)> {
match self
.slots
.binary_search_by(|(index_data, _slot)| index_data.cmp(&index))
{
Ok(index) => {
let (index_checkpoint, slot) = self.slots[index];

Some((slot, self.checkpoints[index_checkpoint]))
}

Err(0) => None,

Err(index_data) => {
let checkpoint = self.checkpoints.get(index)?;

let (index_start, slot_start) = self.slots[index_data - 1];
let slot = slot_start + (index - index_start) as u64 * SLOTS_PER_EPOCH;

Some((slot, *checkpoint))
}
}
self.0.get(index).copied()
}

pub fn last(&self) -> Option<(Slot, Hash256)> {
match self.checkpoints.len() {
0 => None,
len => self.checkpoint_by_index(len - 1),
}
self.0.back().copied()
}

pub fn iter(&self) -> impl DoubleEndedIterator<Item = &(Slot, Hash256)> {
self.0.iter()
}
}

Expand Down Expand Up @@ -216,8 +100,8 @@ fn compare_checkpoints<const COUNT: usize>(
let (slot, checkpoint) = checkpoints.checkpoint(slot_start).unwrap();
assert_eq!(
slot, slot_start,
"start; slot = {slot}, {:?}, {:?}, {data:?}",
checkpoints.slots, checkpoints.checkpoints
"start; slot = {slot}, {:?}, {data:?}",
checkpoints.0
);
assert_eq!(checkpoint, checkpoint_start);

Expand All @@ -226,8 +110,8 @@ fn compare_checkpoints<const COUNT: usize>(
let (slot, checkpoint) = checkpoints.checkpoint(slot_requested).unwrap();
assert_eq!(
slot, slot_end,
"slot = {slot}, slot_requested = {slot_requested}, {:?}, {:?}, {data:?}",
checkpoints.slots, checkpoints.checkpoints
"slot = {slot}, slot_requested = {slot_requested}, {:?}, {data:?}",
checkpoints.0
);
assert_eq!(checkpoint, checkpoint_end);
}
Expand Down Expand Up @@ -282,7 +166,7 @@ fn checkpoints() {

assert!(matches!(
checkpoints.checkpoint(0),
Err(CheckpointError::OutDated),
Ok(result) if result == data[0],
));
assert!(matches!(
checkpoints.checkpoint(u64::MAX),
Expand Down
41 changes: 15 additions & 26 deletions gear-programs/checkpoint-light-client/src/wasm/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use io::ethereum_common::{
base_types::BytesFixed,
beacon::{BLSPubKey, SyncCommittee},
};
use meta::StateRequest;

pub fn construct_sync_committee(
aggregate_pubkey: BLSPubKey,
Expand Down Expand Up @@ -35,40 +36,28 @@ pub fn get_participating_keys(
.iter()
.zip(committee.0.iter())
.filter_map(|(bit, pub_key)| bit.then_some(pub_key.clone().0 .0))
.collect::<Vec<_>>()
.collect()
}

pub fn construct_state_reply(
request: meta::StateRequest,
request: StateRequest,
state: &State<STORED_CHECKPOINTS_COUNT>,
) -> meta::State {
use meta::Order;

let count = Saturating(request.count as usize);
let checkpoints_all = state.checkpoints.checkpoints();
let (start, end) = match request.order {
Order::Direct => {
let start = Saturating(request.index_start as usize);
let Saturating(end) = start + count;

(start.0, cmp::min(end, checkpoints_all.len()))
}

Order::Reverse => {
let len = Saturating(checkpoints_all.len());
let index_last = Saturating(request.index_start as usize);
let end = len - index_last;
let Saturating(start) = end - count;
fn collect<'a, T: 'a + Copy>(
request: &StateRequest,
iter: impl DoubleEndedIterator<Item = &'a T>,
) -> Vec<T> {
iter.skip(request.index_start as usize)
.take(request.count as usize)
.copied()
.collect()
}

(start, end.0)
}
let checkpoints = match request.order {
meta::Order::Direct => collect(&request, state.checkpoints.iter()),
meta::Order::Reverse => collect(&request, state.checkpoints.iter().rev()),
};

let checkpoints = checkpoints_all
.get(start..end)
.map(|slice| slice.to_vec())
.unwrap_or(vec![]);

let replay_back = state
.replay_back
.as_ref()
Expand Down
Loading