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

chore(gear-programs): Add docs to historical-proxy #267

Merged
merged 4 commits into from
Dec 27, 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
31 changes: 22 additions & 9 deletions api/gear/historical_proxy.idl
Original file line number Diff line number Diff line change
@@ -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,
};

Expand All @@ -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<u8>, Vec<u8>)`: 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 };
}
};
Expand Down
8 changes: 6 additions & 2 deletions gear-programs/historical-proxy/app/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
2 changes: 0 additions & 2 deletions gear-programs/historical-proxy/app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ pub struct HistoricalProxyProgram(RefCell<state::ProxyState>);

#[sails_rs::program]
impl HistoricalProxyProgram {
// Program's constructor
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let exec_context = GStdExecContext::new();
Expand All @@ -28,7 +27,6 @@ impl HistoricalProxyProgram {
}))
}

// Exposed service
pub fn historical_proxy(&self) -> service::HistoricalProxyService<GStdExecContext> {
service::HistoricalProxyService::new(&self.0, GStdExecContext::new())
}
Expand Down
49 changes: 39 additions & 10 deletions gear-programs/historical-proxy/app/src/service.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<ProxyState>,
exec_context: ExecContext,
Expand All @@ -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<ActorId, ProxyError> {
/// Get endpoint for the specified `slot`.
pub fn endpoint_for(&self, slot: Slot) -> Result<ActorId, ProxyError> {
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<u8>, Vec<u8>)`: 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,
Expand Down
21 changes: 15 additions & 6 deletions gear-programs/historical-proxy/app/src/state.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<ActorId, ProxyError> {
match self.0.binary_search_by(|(s, _)| s.cmp(&slot)) {
Ok(i) => Ok(self.0[i].1),
Expand Down
10 changes: 4 additions & 6 deletions gear-programs/historical-proxy/app/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,19 @@ 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();

assert_eq!(recv_endpoint, Ok(endpoint1.1));

let recv_endpoint = HistoricalProxyC::new(remoting.clone())
.endpoint_for(41)
.send_recv(proxy_program_id)
.recv(proxy_program_id)
.await
.unwrap();

Expand All @@ -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();

Expand Down
3 changes: 1 addition & 2 deletions gear-programs/historical-proxy/app/tests/gclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading