helios/consensus/src/rpc/nimbus_rpc.rs

170 lines
4.3 KiB
Rust

use std::cmp;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::Arc;
use async_trait::async_trait;
use eyre::{Result, WrapErr};
use serde::Deserialize;
use serde_json::from_str as from_json_str;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use crate::constants::MAX_REQUEST_LIGHT_CLIENT_UPDATES;
use crate::types::*;
use super::ConsensusRpc;
#[derive(Debug)]
pub struct RpcError {
message: String,
}
impl Display for RpcError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.message)
}
}
impl std::error::Error for RpcError {}
#[derive(Debug)]
pub struct NimbusRpc {
rpc_handler: Arc<JsValue>,
}
#[async_trait(?Send)]
impl ConsensusRpc for NimbusRpc {
fn new(rpc_handler: Arc<JsValue>) -> Self {
NimbusRpc { rpc_handler }
}
async fn get_bootstrap(&self, block_root: &'_ [u8]) -> Result<Bootstrap> {
let root_hex = hex::encode(block_root);
let path = format!("/eth/v1/beacon/light_client/bootstrap/0x{}", root_hex);
let res = self.request::<Bootstrap>(path, "bootstrap").await?;
Ok(res)
}
async fn get_updates(&self, period: u64, count: u8) -> Result<Vec<Update>> {
let count = cmp::min(count, MAX_REQUEST_LIGHT_CLIENT_UPDATES);
let path = format!(
"/eth/v1/beacon/light_client/updates?start_period={}&count={}",
period, count
);
let res = self.request::<UpdateResponse>(path, "updates").await?;
Ok(res.iter().map(|d| d.data.clone()).collect())
}
async fn get_finality_update(&self) -> Result<FinalityUpdate> {
let path = format!("/eth/v1/beacon/light_client/finality_update");
let res = self
.request::<FinalityUpdateResponse>(path, "finality_update")
.await?;
Ok(res.data)
}
async fn get_optimistic_update(&self) -> Result<OptimisticUpdate> {
let path = format!("/eth/v1/beacon/light_client/optimistic_update");
let res = self
.request::<OptimisticUpdateResponse>(path, "optimistic_update")
.await?;
Ok(res.data)
}
async fn get_block(&self, slot: u64) -> Result<BeaconBlock> {
let path = format!("/eth/v2/beacon/blocks/{}", slot);
let res = self.request::<BeaconBlockResponse>(path, "blocks").await?;
Ok(res.data.message)
}
async fn chain_id(&self) -> Result<u64> {
let path = format!("/eth/v1/config/spec");
let res = self.request::<SpecResponse>(path, "blocks").await?;
Ok(res.data.chain_id)
}
}
#[derive(serde::Deserialize, Debug)]
struct BeaconBlockResponse {
data: BeaconBlockData,
}
#[derive(serde::Deserialize, Debug)]
struct BeaconBlockData {
message: BeaconBlock,
}
type UpdateResponse = Vec<UpdateData>;
#[derive(serde::Deserialize, Debug)]
struct UpdateData {
data: Update,
}
#[derive(serde::Deserialize, Debug)]
struct FinalityUpdateResponse {
data: FinalityUpdate,
}
#[derive(serde::Deserialize, Debug)]
struct OptimisticUpdateResponse {
data: OptimisticUpdate,
}
#[derive(serde::Deserialize, Debug)]
struct BootstrapResponse {
data: Bootstrap,
}
#[derive(serde::Deserialize, Debug)]
struct SpecResponse {
data: Spec,
}
#[derive(serde::Deserialize, Debug)]
struct Spec {
#[serde(rename = "DEPOSIT_NETWORK_ID", deserialize_with = "u64_deserialize")]
chain_id: u64,
}
impl NimbusRpc {
async fn request<T>(&self, path: String, error_type: &str) -> Result<T>
where
for<'a> T: Deserialize<'a>,
{
let js_params = js_sys::Map::new();
js_params.set(&JsValue::from_str("method"), &JsValue::from_str("GET"));
js_params.set(&JsValue::from_str("path"), &JsValue::from_str(&path));
let rpc_handler_clone = self.rpc_handler.clone();
let promise = Arc::clone(&rpc_handler_clone)
.dyn_into::<js_sys::Function>()
.unwrap()
.call1(&JsValue::null(), &js_params)
.expect("Failed to make JSON-RPC request");
let result = match promise.dyn_into::<js_sys::Promise>() {
Ok(promise) => JsFuture::from(promise).await.unwrap(),
Err(_) => panic!("Unable to convert JS value to Promise"),
};
let json = result.as_string().unwrap();
let response: T = serde_json::from_str(&json)?;
Ok(response)
}
}