From 8b1018bd8a2b882a2b9d4fc84c12666144b54efd Mon Sep 17 00:00:00 2001 From: mertwole <33563701+mertwole@users.noreply.github.com> Date: Fri, 27 Dec 2024 16:58:00 +0400 Subject: [PATCH] chore(gear-programs): Add docs to `historical-proxy` (#267) --- api/gear/historical_proxy.idl | 31 ++++++++---- .../historical-proxy/app/src/error.rs | 8 ++- gear-programs/historical-proxy/app/src/lib.rs | 2 - .../historical-proxy/app/src/service.rs | 49 +++++++++++++++---- .../historical-proxy/app/src/state.rs | 21 +++++--- .../historical-proxy/app/src/tests.rs | 10 ++-- .../historical-proxy/app/tests/gclient.rs | 3 +- 7 files changed, 87 insertions(+), 37 deletions(-) diff --git a/api/gear/historical_proxy.idl b/api/gear/historical_proxy.idl index 4f2d21eb..fa328fa6 100644 --- a/api/gear/historical_proxy.idl +++ b/api/gear/historical_proxy.idl @@ -1,10 +1,14 @@ +/// Errors returned by the Historical Proxy service. type ProxyError = enum { + /// Endpoint for requested slot not found. NoEndpointForSlot: u64, + /// Failed to send message. SendFailure: str, - ReplyTimeout: str, + /// Failed to receive reply. ReplyFailure: str, + /// Failed to decode reply. DecodeFailure: str, - NotAdmin, + /// `ethereum-event-client` returned error. EthereumEventClient: Error, }; @@ -29,28 +33,37 @@ constructor { }; service HistoricalProxy { - AddEndpoint : (slot: u64, endpoint: actor_id) -> result (null, ProxyError); - EndpointFor : (slot: u64) -> result (actor_id, ProxyError); - /// Redirect message to ERC20 Relay service which is valid for `slot`. - /// If message is relayed successfully then reply from relay service is sent to - /// `client` address and proofs are returned. + /// Add new endpoint to the map. Endpoint will be effective for all the + /// requests with slots starting from `slot`. + /// + /// This function can be called only by an admin. + AddEndpoint : (slot: u64, endpoint: actor_id) -> null; + /// Redirect message to `ethereum-event-client` program which is valid for `slot`. + /// If message is relayed successfully then reply is sent to `client` address + /// to `client_route` route. /// /// # Parameters /// /// - `slot`: slot for which message is relayed. - /// - `tx_index`: transaction index for message. /// - `proofs`: SCALE encoded `EthToVaraEvent`. /// - `client`: client address to send receipt to on success. /// - `client_route`: route to send receipt to on success. + /// /// # Returns + /// /// - `(Vec, Vec)`: on success where first vector is receipt and second vector is reply from calling `client_route`. /// - `ProxyError`: if redirect failed - /// Redirect : (slot: u64, proofs: vec u8, client: actor_id, client_route: vec u8) -> result (struct { vec u8, vec u8 }, ProxyError); + /// Get current service admin. query Admin : () -> actor_id; + /// Get endpoint for the specified `slot`. + query EndpointFor : (slot: u64) -> result (actor_id, ProxyError); + /// Get endpoint map stored in this service. query Endpoints : () -> vec struct { u64, actor_id }; events { + /// Tx receipt is checked to be valid and successfully sent to the + /// underlying program. Relayed: struct { slot: u64, block_number: u64, transaction_index: u32 }; } }; diff --git a/gear-programs/historical-proxy/app/src/error.rs b/gear-programs/historical-proxy/app/src/error.rs index d5e8723d..d0429edc 100644 --- a/gear-programs/historical-proxy/app/src/error.rs +++ b/gear-programs/historical-proxy/app/src/error.rs @@ -3,15 +3,19 @@ use parity_scale_codec::{Decode, Encode}; use sails_rs::prelude::String; use scale_info::TypeInfo; +/// Errors returned by the Historical Proxy service. #[derive(Debug, Decode, Encode, TypeInfo)] #[codec(crate = sails_rs::scale_codec)] #[scale_info(crate = sails_rs::scale_info)] pub enum ProxyError { + /// Endpoint for requested slot not found. NoEndpointForSlot(u64), + /// Failed to send message. SendFailure(String), - ReplyTimeout(String), + /// Failed to receive reply. ReplyFailure(String), + /// Failed to decode reply. DecodeFailure(String), - NotAdmin, + /// `ethereum-event-client` returned error. EthereumEventClient(ethereum_event_client::Error), } diff --git a/gear-programs/historical-proxy/app/src/lib.rs b/gear-programs/historical-proxy/app/src/lib.rs index f44a677b..2c56b17e 100644 --- a/gear-programs/historical-proxy/app/src/lib.rs +++ b/gear-programs/historical-proxy/app/src/lib.rs @@ -18,7 +18,6 @@ pub struct HistoricalProxyProgram(RefCell); #[sails_rs::program] impl HistoricalProxyProgram { - // Program's constructor #[allow(clippy::new_without_default)] pub fn new() -> Self { let exec_context = GStdExecContext::new(); @@ -28,7 +27,6 @@ impl HistoricalProxyProgram { })) } - // Exposed service pub fn historical_proxy(&self) -> service::HistoricalProxyService { service::HistoricalProxyService::new(&self.0, GStdExecContext::new()) } diff --git a/gear-programs/historical-proxy/app/src/service.rs b/gear-programs/historical-proxy/app/src/service.rs index 75d1373e..eec46932 100644 --- a/gear-programs/historical-proxy/app/src/service.rs +++ b/gear-programs/historical-proxy/app/src/service.rs @@ -1,5 +1,4 @@ // Incorporate code generated based on the IDL file - #[allow(dead_code)] #[allow(clippy::module_inception)] pub(crate) mod ethereum_event_client { @@ -16,17 +15,41 @@ use crate::{ state::{ProxyState, Slot}, }; +/// Events enmitted by the Historical Proxy service. #[derive(Encode, TypeInfo)] #[codec(crate = sails_rs::scale_codec)] #[scale_info(crate = sails_rs::scale_info)] enum Event { + /// Tx receipt is checked to be valid and successfully sent to the + /// underlying program. Relayed { + /// Ethereum slot containing target transaction. slot: u64, + /// Ethereum block number which contains target transaction. block_number: u64, + /// Index of the target transaction in the `block_number`. transaction_index: u32, }, } +/// Historical Proxy service. +/// +/// `etereum-event-client` programs can become outdated with Ethereum updates, so +/// every `ethereum-event-client` ever deployed is valid for some Ethereum slot interval. +/// +/// When Ethereum updates in a way incompatible with `ethereum-event-cleint`(or if we need to +/// update `ethereum-event-client` for some other reason) we need to deploy a new version of +/// `ethereum-event-client` and still have access to the old one(in order to process +/// historical transactions). +/// +/// This service provides such an access. For every `ethereum-event-client` ever deployed +/// it maps Ethereum slot from which this `ethereum-event-client` is valid from. +/// +/// When user makes request to the Historical Proxy service he will specify Ethereum slot number +/// where the target transaction was sent. Historical Proxy will decide which `ethereum-event-client` +/// is responsible of processing transactions for this slot and will redirect user request to it. +/// If `ethereum-event-client` returned success its reply will be redirected to the program +/// that user have specified in his request. For more info see `redirect` implementation. pub struct HistoricalProxyService<'a, ExecContext> { state: &'a RefCell, exec_context: ExecContext, @@ -44,45 +67,51 @@ where } } + /// Get current service admin. pub fn admin(&self) -> ActorId { self.state.borrow().admin } - pub fn endpoint_for(&mut self, slot: Slot) -> Result { + /// Get endpoint for the specified `slot`. + pub fn endpoint_for(&self, slot: Slot) -> Result { self.state.borrow().endpoints.endpoint_for(slot) } - pub fn add_endpoint(&mut self, slot: Slot, endpoint: ActorId) -> Result<(), ProxyError> { + /// Add new endpoint to the map. Endpoint will be effective for all the + /// requests with slots starting from `slot`. + /// + /// This function can be called only by an admin. + pub fn add_endpoint(&mut self, slot: Slot, endpoint: ActorId) { let source = self.exec_context.actor_id(); let mut state = self.state.borrow_mut(); if source != state.admin { - return Err(ProxyError::NotAdmin); + panic!("Not an admin"); } state.endpoints.push(slot, endpoint); - Ok(()) } + /// Get endpoint map stored in this service. pub fn endpoints(&self) -> Vec<(Slot, ActorId)> { self.state.borrow().endpoints.endpoints() } - /// Redirect message to ERC20 Relay service which is valid for `slot`. - /// If message is relayed successfully then reply from relay service is sent to - /// `client` address and proofs are returned. + /// Redirect message to `ethereum-event-client` program which is valid for `slot`. + /// If message is relayed successfully then reply is sent to `client` address + /// to `client_route` route. /// /// # Parameters /// /// - `slot`: slot for which message is relayed. - /// - `tx_index`: transaction index for message. /// - `proofs`: SCALE encoded `EthToVaraEvent`. /// - `client`: client address to send receipt to on success. /// - `client_route`: route to send receipt to on success. + /// /// # Returns + /// /// - `(Vec, Vec)`: on success where first vector is receipt and second vector is reply from calling `client_route`. /// - `ProxyError`: if redirect failed - /// #[allow(clippy::await_holding_refcell_ref)] pub async fn redirect( &mut self, diff --git a/gear-programs/historical-proxy/app/src/state.rs b/gear-programs/historical-proxy/app/src/state.rs index 7deb1d28..d4cbf6cf 100644 --- a/gear-programs/historical-proxy/app/src/state.rs +++ b/gear-programs/historical-proxy/app/src/state.rs @@ -1,25 +1,30 @@ use super::error::ProxyError; use super::{ActorId, Vec}; + pub type Slot = u64; +/// State of the Historical Proxy service. pub struct ProxyState { pub admin: ActorId, pub endpoints: EndpointList, } +/// Mapping between endpoints and Ethereum slots they're active from. +/// +/// ### Invariant +/// +/// Endpoints are stored in ascending order, sorted by slot number. +#[derive(Default)] pub struct EndpointList(Vec<(Slot, ActorId)>); -impl Default for EndpointList { - fn default() -> Self { - Self::new() - } -} - impl EndpointList { pub fn new() -> Self { Self(Vec::with_capacity(2)) } + /// Add new endpoint that will be active starting from `slot`(inclusive). + /// + /// Panics if provided `slot` <= greatest already existing slot. pub fn push(&mut self, slot: Slot, actor_id: ActorId) { assert!( self.0.is_empty() || self.0[self.0.len() - 1].0 < slot, @@ -28,10 +33,14 @@ impl EndpointList { self.0.push((slot, actor_id)); } + /// Get list of currently active endpoints. Returns `Vec<(Slot, ActorId)>` + /// where `ActorId` means endpoint address and `Slot` means Ethereum slot + /// this endpoint is active from(inclusive). pub fn endpoints(&self) -> Vec<(Slot, ActorId)> { self.0.clone() } + /// Get endpoint for the specified slot. Will return error if endpoint is not found. pub fn endpoint_for(&self, slot: Slot) -> Result { match self.0.binary_search_by(|(s, _)| s.cmp(&slot)) { Ok(i) => Ok(self.0[i].1), diff --git a/gear-programs/historical-proxy/app/src/tests.rs b/gear-programs/historical-proxy/app/src/tests.rs index 5184810f..3d541b75 100644 --- a/gear-programs/historical-proxy/app/src/tests.rs +++ b/gear-programs/historical-proxy/app/src/tests.rs @@ -59,12 +59,11 @@ async fn test_utility_functions() { .add_endpoint(42, ActorId::from(0x42)) .send_recv(proxy_program_id) .await - .unwrap() .unwrap(); let recv_endpoint = HistoricalProxyC::new(remoting.clone()) .endpoint_for(43) - .send_recv(proxy_program_id) + .recv(proxy_program_id) .await .unwrap(); @@ -72,7 +71,7 @@ async fn test_utility_functions() { let recv_endpoint = HistoricalProxyC::new(remoting.clone()) .endpoint_for(41) - .send_recv(proxy_program_id) + .recv(proxy_program_id) .await .unwrap(); @@ -93,19 +92,18 @@ async fn test_utility_functions() { .add_endpoint(84, ActorId::from(0x800)) .send_recv(proxy_program_id) .await - .unwrap() .unwrap(); let endpoint_for_slot_0 = HistoricalProxyC::new(remoting.clone()) .endpoint_for(43) - .send_recv(proxy_program_id) + .recv(proxy_program_id) .await .unwrap(); assert_eq!(endpoint_for_slot_0, Ok(ActorId::from(0x42))); let endpoint_for_slot_1 = HistoricalProxyC::new(remoting.clone()) .endpoint_for(85) - .send_recv(proxy_program_id) + .recv(proxy_program_id) .await .unwrap(); diff --git a/gear-programs/historical-proxy/app/tests/gclient.rs b/gear-programs/historical-proxy/app/tests/gclient.rs index 66415b35..d4cad8f8 100644 --- a/gear-programs/historical-proxy/app/tests/gclient.rs +++ b/gear-programs/historical-proxy/app/tests/gclient.rs @@ -106,12 +106,11 @@ async fn proxy() { ) .send_recv(proxy_program_id) .await - .unwrap() .unwrap(); let endpoint = proxy_client .endpoint_for(message.proof_block.block.slot) - .send_recv(proxy_program_id) + .recv(proxy_program_id) .await .unwrap() .unwrap();