diff --git a/gear-programs/checkpoint-light-client/io/src/lib.rs b/gear-programs/checkpoint-light-client/io/src/lib.rs index 096d228e..e717717e 100644 --- a/gear-programs/checkpoint-light-client/io/src/lib.rs +++ b/gear-programs/checkpoint-light-client/io/src/lib.rs @@ -80,6 +80,7 @@ pub enum Handle { headers: Vec, }, ReplayBack(Vec), + GetState(meta::StateRequest), } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -88,4 +89,5 @@ pub enum HandleResult { SyncUpdate(Result<(), sync_update::Error>), ReplayBackStart(Result), ReplayBack(Option), + State(meta::State), } diff --git a/gear-programs/checkpoint-light-client/io/src/meta.rs b/gear-programs/checkpoint-light-client/io/src/meta.rs index 237bdd55..ce96ee54 100644 --- a/gear-programs/checkpoint-light-client/io/src/meta.rs +++ b/gear-programs/checkpoint-light-client/io/src/meta.rs @@ -1,6 +1,6 @@ use super::*; -use gmeta::{In, InOut, Out}; +use gmeta::{In, InOut}; use gstd::prelude::*; pub struct Metadata; @@ -11,13 +11,35 @@ impl gmeta::Metadata for Metadata { type Others = (); type Reply = (); type Signal = (); - type State = Out; + type State = InOut; } #[derive(Debug, Clone, Default, Encode, Decode, TypeInfo)] pub struct State { pub checkpoints: Vec<(Slot, Hash256)>, - /// The field contains the last processed header if the program is + /// The field contains the data if the program is /// replaying checkpoints back. - pub replay_back: Option, + pub replay_back: Option, +} + +/// The struct contains slots of the finalized and the last checked headers. +#[derive(Debug, Clone, Encode, Decode, TypeInfo)] +pub struct ReplayBack { + pub finalized_header: Slot, + pub last_header: Slot, +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo)] +pub enum Order { + Direct, + Reverse, +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo)] +pub struct StateRequest { + // The flag specifies the subslice position + pub order: Order, + // Parameters determine checkpoints count in the response + pub index_start: u32, + pub count: u32, } diff --git a/gear-programs/checkpoint-light-client/io/src/sync_update.rs b/gear-programs/checkpoint-light-client/io/src/sync_update.rs index f852f8e8..81e30486 100644 --- a/gear-programs/checkpoint-light-client/io/src/sync_update.rs +++ b/gear-programs/checkpoint-light-client/io/src/sync_update.rs @@ -27,7 +27,7 @@ pub enum Error { InvalidNextSyncCommitteeProof, InvalidPublicKeys, ReplayBackRequired { - replayed_slot: Option, + replay_back: Option, checkpoint: (Slot, Hash256), }, } diff --git a/gear-programs/checkpoint-light-client/src/wasm/mod.rs b/gear-programs/checkpoint-light-client/src/wasm/mod.rs index d4b46f02..bbd0217a 100644 --- a/gear-programs/checkpoint-light-client/src/wasm/mod.rs +++ b/gear-programs/checkpoint-light-client/src/wasm/mod.rs @@ -1,5 +1,6 @@ use super::*; use ark_serialize::CanonicalSerialize; +use core::{cmp, num::Saturating}; use gstd::{msg, vec}; use io::{ ethereum_common::{ @@ -9,6 +10,7 @@ use io::{ tree_hash::TreeHash, utils as eth_utils, Hash256, SYNC_COMMITTEE_SIZE, }, + meta, sync_update::Error as SyncCommitteeUpdateError, BeaconBlockHeader, Handle, HandleResult, Init, SyncCommitteeKeys, SyncCommitteeUpdate, G1, G2, }; @@ -107,28 +109,21 @@ async fn main() { } => replay_back::handle_start(state, sync_update, headers).await, Handle::ReplayBack(headers) => replay_back::handle(state, headers), + + Handle::GetState(request) => { + let reply = utils::construct_state_reply(request, state); + msg::reply(HandleResult::State(reply), 0) + .expect("Unable to reply with `HandleResult::State`"); + } } } #[no_mangle] extern "C" fn state() { - let state = unsafe { STATE.as_ref() }; - let checkpoints = state - .map(|state| state.checkpoints.checkpoints()) - .unwrap_or(vec![]); - let replay_back = state.and_then(|state| { - state - .replay_back - .as_ref() - .map(|replay_back| replay_back.last_header.clone()) - }); - - msg::reply( - io::meta::State { - checkpoints, - replay_back, - }, - 0, - ) - .expect("Failed to encode or reply with `::State` from `state()`"); + let request = msg::load().expect("Unable to decode `StateRequest` message"); + let state = unsafe { STATE.as_ref() }.expect("The program should be initialized"); + let reply = utils::construct_state_reply(request, state); + + msg::reply(reply, 0) + .expect("Failed to encode or reply with `::State` from `state()`"); } diff --git a/gear-programs/checkpoint-light-client/src/wasm/sync_update.rs b/gear-programs/checkpoint-light-client/src/wasm/sync_update.rs index c09b286b..19193d06 100644 --- a/gear-programs/checkpoint-light-client/src/wasm/sync_update.rs +++ b/gear-programs/checkpoint-light-client/src/wasm/sync_update.rs @@ -28,10 +28,13 @@ pub async fn handle(state: &mut State, sync_update: Sy { let result = HandleResult::SyncUpdate(Err(io::sync_update::Error::ReplayBackRequired { - replayed_slot: state + replay_back: state .replay_back .as_ref() - .map(|replay_back| replay_back.last_header.slot), + .map(|replay_back| meta::ReplayBack { + finalized_header: replay_back.finalized_header.slot, + last_header: replay_back.last_header.slot, + }), checkpoint: state .checkpoints .last() diff --git a/gear-programs/checkpoint-light-client/src/wasm/utils.rs b/gear-programs/checkpoint-light-client/src/wasm/utils.rs index be30578c..ae8edd9a 100644 --- a/gear-programs/checkpoint-light-client/src/wasm/utils.rs +++ b/gear-programs/checkpoint-light-client/src/wasm/utils.rs @@ -1,11 +1,7 @@ use super::*; -use io::{ - ethereum_common::{ - base_types::{BytesFixed, FixedArray}, - beacon::{BLSPubKey, SyncCommittee}, - SYNC_COMMITTEE_SIZE, - }, - SyncCommitteeKeys, +use io::ethereum_common::{ + base_types::BytesFixed, + beacon::{BLSPubKey, SyncCommittee}, }; pub fn construct_sync_committee( @@ -41,3 +37,48 @@ pub fn get_participating_keys( .filter_map(|(bit, pub_key)| bit.then_some(pub_key.clone().0 .0)) .collect::>() } + +pub fn construct_state_reply( + request: meta::StateRequest, + state: &State, +) -> 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; + + (start, end.0) + } + }; + + let checkpoints = checkpoints_all + .get(start..end) + .map(|slice| slice.to_vec()) + .unwrap_or(vec![]); + + let replay_back = state + .replay_back + .as_ref() + .map(|replay_back| meta::ReplayBack { + finalized_header: replay_back.finalized_header.slot, + last_header: replay_back.last_header.slot, + }); + + meta::State { + checkpoints, + replay_back, + } +} diff --git a/relayer/src/ethereum_checkpoints/mod.rs b/relayer/src/ethereum_checkpoints/mod.rs index 635841e0..01c10620 100644 --- a/relayer/src/ethereum_checkpoints/mod.rs +++ b/relayer/src/ethereum_checkpoints/mod.rs @@ -101,7 +101,7 @@ pub async fn relay(args: RelayCheckpointsArgs) { return; } Ok(Err(sync_update::Error::ReplayBackRequired { - replayed_slot, + replay_back, checkpoint, })) => { if let Err(e) = replay_back::execute( @@ -111,7 +111,7 @@ pub async fn relay(args: RelayCheckpointsArgs) { &mut listener, program_id, gas_limit, - replayed_slot, + replay_back.map(|r| r.last_header), checkpoint, sync_update, )