Skip to content
Closed
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ dirs = "5.0.1"
serde = { workspace = true }
serde_json = { workspace = true }
tracing.workspace = true
async-trait = "0.1"
reqwest = { workspace = true, features = ["json"] }
138 changes: 138 additions & 0 deletions rpc/src/client/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use summit_types::rpc::{
CheckpointInfoRes, CheckpointRes, DepositTransactionResponse, PublicKeysResponse,
ServerModeResponse,
};

use crate::client::{traits::*, types::ClientError};

/// HTTP client for Summit RPC
pub struct Client {
base_url: String,
client: reqwest::Client,
}

impl Client {
/// Create a new client with the given base URL
pub fn new(url: impl Into<String>) -> Self {
Self {
base_url: url.into(),
client: reqwest::Client::new(),
}
}

fn url(&self, path: &str) -> String {
format!(
"{}/{}",
self.base_url.trim_end_matches('/'),
path.trim_start_matches('/')
)
}
}

#[async_trait::async_trait]
impl SummitClient for Client {
async fn health(&self) -> Result<String, reqwest::Error> {
self.client
.get(self.url("health"))
.send()
.await?
.text()
.await
}

async fn server_mode(&self) -> Result<ServerModeResponse, reqwest::Error> {
self.client
.get(self.url("server_mode"))
.send()
.await?
.json()
.await
}

async fn get_public_keys(&self) -> Result<PublicKeysResponse, reqwest::Error> {
self.client
.get(self.url("get_public_keys"))
.send()
.await?
.json()
.await
}
}

#[async_trait::async_trait]
impl GenesisClient for Client {
async fn send_genesis(&self, body: String) -> Result<String, reqwest::Error> {
self.client
.post(self.url("send_genesis"))
.body(body)
.send()
.await?
.text()
.await
}
}

#[async_trait::async_trait]
impl NodeClient for Client {
async fn get_checkpoint(&self, epoch: u64) -> Result<CheckpointRes, reqwest::Error> {
self.client
.get(self.url(&format!("get_checkpoint/{}", epoch)))
.send()
.await?
.json()
.await
}

async fn get_latest_checkpoint(&self) -> Result<CheckpointRes, reqwest::Error> {
self.client
.get(self.url("get_latest_checkpoint"))
.send()
.await?
.json()
.await
}

async fn get_latest_checkpoint_info(&self) -> Result<CheckpointInfoRes, reqwest::Error> {
self.client
.get(self.url("get_latest_checkpoint_info"))
.send()
.await?
.json()
.await
}

async fn get_latest_height(&self) -> Result<u64, ClientError> {
let text = self
.client
.get(self.url("get_latest_height"))
.send()
.await?
.text()
.await?;
Ok(text.parse()?)
}

async fn get_validator_balance(&self, public_key: &str) -> Result<u64, ClientError> {
let text = self
.client
.get(self.url(&format!("get_validator_balance?public_key={}", public_key)))
.send()
.await?
.text()
.await?;
Ok(text.parse()?)
}

async fn get_deposit_signature(
&self,
amount: u64,
address: &str,
) -> Result<DepositTransactionResponse, reqwest::Error> {
self.client
.get(self.url(&format!("get_deposit_signature/{}/{}", amount, address)))
.send()
.await?
.json()
.await
}
}
36 changes: 36 additions & 0 deletions rpc/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Summit RPC client
//!
//! # Example
//!
//! ```no_run
//! use summit_rpc::client::{Client, SummitClient, NodeClient, GenesisClient};
//!
//! #[tokio::main]
//! async fn main() {
//! let client = Client::new("http://localhost:8080");
//!
//! // Check server mode
//! let mode = client.server_mode().await.unwrap();
//!
//! // Use node-specific methods
//! let height = client.get_latest_height().await.unwrap();
//!
//! // Use genesis-specific methods
//! let genesis = std::fs::read_to_string("genesis.json").unwrap();
//! client.send_genesis(genesis).await.unwrap();
//! }
//! ```

mod client;
mod traits;
mod types;

pub use client::*;
pub use traits::*;
pub use types::*;

// Re-export API response types from summit_types for convenience
pub use summit_types::rpc::{
CheckpointInfoRes, CheckpointRes, DepositTransactionResponse, PublicKeysResponse, ServerMode,
ServerModeResponse,
};
52 changes: 52 additions & 0 deletions rpc/src/client/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use summit_types::rpc::{
CheckpointInfoRes, CheckpointRes, DepositTransactionResponse, PublicKeysResponse,
ServerModeResponse,
};

use crate::client::types::ClientError;

/// Shared client methods available in both genesis and node modes
#[async_trait::async_trait]
pub trait SummitClient {
/// Health check
async fn health(&self) -> Result<String, reqwest::Error>;

/// Get server mode (genesis or node)
async fn server_mode(&self) -> Result<ServerModeResponse, reqwest::Error>;

/// Get node and consensus public keys
async fn get_public_keys(&self) -> Result<PublicKeysResponse, reqwest::Error>;
}

/// Genesis-specific client methods
#[async_trait::async_trait]
pub trait GenesisClient: SummitClient {
/// Send genesis file
async fn send_genesis(&self, body: String) -> Result<String, reqwest::Error>;
}

/// Node-specific client methods
#[async_trait::async_trait]
pub trait NodeClient: SummitClient {
/// Get checkpoint by epoch
async fn get_checkpoint(&self, epoch: u64) -> Result<CheckpointRes, reqwest::Error>;

/// Get latest checkpoint
async fn get_latest_checkpoint(&self) -> Result<CheckpointRes, reqwest::Error>;

/// Get latest checkpoint info
async fn get_latest_checkpoint_info(&self) -> Result<CheckpointInfoRes, reqwest::Error>;

/// Get latest height
async fn get_latest_height(&self) -> Result<u64, ClientError>;

/// Get validator balance by public key
async fn get_validator_balance(&self, public_key: &str) -> Result<u64, ClientError>;

/// Get deposit signature
async fn get_deposit_signature(
&self,
amount: u64,
address: &str,
) -> Result<DepositTransactionResponse, reqwest::Error>;
}
30 changes: 30 additions & 0 deletions rpc/src/client/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::num::ParseIntError;

#[derive(Debug)]
pub enum ClientError {
Request(reqwest::Error),
Parse(ParseIntError),
}

impl From<reqwest::Error> for ClientError {
fn from(e: reqwest::Error) -> Self {
ClientError::Request(e)
}
}

impl From<ParseIntError> for ClientError {
fn from(e: ParseIntError) -> Self {
ClientError::Parse(e)
}
}

impl std::fmt::Display for ClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClientError::Request(e) => write!(f, "request error: {}", e),
ClientError::Parse(e) => write!(f, "parse error: {}", e),
}
}
}

impl std::error::Error for ClientError {}
1 change: 1 addition & 0 deletions rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod client;
pub mod routes;
use crate::routes::RpcRoutes;
use commonware_consensus::Block as ConsensusBlock;
Expand Down
37 changes: 19 additions & 18 deletions rpc/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,23 @@ use commonware_consensus::Block as ConsensusBlock;
use commonware_consensus::simplex::signing_scheme::Scheme;
use commonware_cryptography::{Committable, Hasher as _, Sha256, Signer as _};
use commonware_utils::from_hex_formatted;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use ssz::Encode;
use summit_types::{
KeyPaths, PROTOCOL_VERSION, PublicKey,
execution_request::{DepositRequest, compute_deposit_data_root},
rpc::{CheckpointInfoRes, CheckpointRes},
rpc::{
CheckpointInfoRes, CheckpointRes, DepositTransactionResponse, PublicKeysResponse,
ServerMode, ServerModeResponse,
},
utils::get_expanded_path,
};

#[derive(Serialize)]
struct PublicKeysResponse {
node: String,
consensus: String,
}

#[derive(Deserialize)]
struct ValidatorBalanceQuery {
public_key: String,
}

#[derive(Serialize, Deserialize)]
struct DepositTransactionResponse {
node_pubkey: [u8; 32],
consensus_pubkey: Vec<u8>, // 48 bytes
withdrawal_credentials: [u8; 32],
node_signature: Vec<u8>, // 48 bytes
consensus_signature: Vec<u8>, // 96 bytes
deposit_data_root: [u8; 32],
}

pub(crate) struct RpcRoutes;

impl RpcRoutes {
Expand All @@ -53,6 +40,7 @@ impl RpcRoutes {

Router::new()
.route("/health", get(Self::handle_health_check))
.route("/server_mode", get(Self::handle_server_mode))
.route("/get_public_keys", get(Self::handle_get_pub_keys::<S, B>))
.route(
"/get_checkpoint/{epoch}",
Expand Down Expand Up @@ -87,6 +75,7 @@ impl RpcRoutes {

Router::new()
.route("/health", get(Self::handle_health_check))
.route("/server_mode", get(Self::handle_server_mode_genesis))
.route("/get_public_keys", get(Self::handle_get_pub_keys_genesis))
.route("/send_genesis", post(Self::handle_send_genesis))
.with_state(state)
Expand All @@ -96,6 +85,18 @@ impl RpcRoutes {
"Ok"
}

async fn handle_server_mode() -> Json<ServerModeResponse> {
Json(ServerModeResponse {
mode: ServerMode::Node,
})
}

async fn handle_server_mode_genesis() -> Json<ServerModeResponse> {
Json(ServerModeResponse {
mode: ServerMode::Genesis,
})
}

async fn handle_get_pub_keys<S: Scheme, B: ConsensusBlock + Committable>(
State(state): State<Arc<RpcState<S, B>>>,
) -> Result<String, String> {
Expand Down
Loading