Skip to content
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
84 changes: 78 additions & 6 deletions client/crates/rollkit-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,59 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = RollkitClient::connect("http://localhost:50051").await?;

// Check health
let mut health = HealthClient::new(&client);
let health = HealthClient::new(&client);
let is_healthy = health.is_healthy().await?;
println!("Node healthy: {}", is_healthy);

Ok(())
}
```

### Advanced Configuration
### Using the Builder Pattern

```rust
use rollkit_client::RollkitClient;
use std::time::Duration;

// Create a client with custom timeouts
let client = RollkitClient::builder()
.endpoint("http://localhost:50051")
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(10))
.build()
.await?;
```

### TLS Configuration

```rust
use rollkit_client::{RollkitClient, ClientTlsConfig};

// Enable TLS with default configuration
let client = RollkitClient::builder()
.endpoint("https://secure-node.rollkit.dev")
.tls()
.build()
.await?;

// Or with custom TLS configuration
let tls_config = ClientTlsConfig::new()
.domain_name("secure-node.rollkit.dev");

let client = RollkitClient::builder()
.endpoint("https://secure-node.rollkit.dev")
.tls_config(tls_config)
.build()
.await?;
```

### Legacy Connection Method

```rust
use rollkit_client::RollkitClient;
use std::time::Duration;

// Still supported for backward compatibility
let client = RollkitClient::connect_with_config(
"http://localhost:50051",
|endpoint| {
Expand All @@ -67,7 +106,7 @@ let client = RollkitClient::connect_with_config(

## Services

The client provides wrappers for all Rollkit gRPC services:
The client provides wrappers for all Rollkit gRPC services. All service methods are now thread-safe and can be called concurrently:

### Health Service

Expand All @@ -86,9 +125,10 @@ The client provides wrappers for all Rollkit gRPC services:

### Store Service

- `get_block(height)` - Get a block by height
- `get_state(height)` - Get state at a specific height
- `get_metadata(initial_height, latest_height)` - Get metadata for a height range
- `get_block_by_height(height)` - Get a block by height
- `get_block_by_hash(hash)` - Get a block by hash
- `get_state()` - Get the current state
- `get_metadata(key)` - Get metadata by key

## Examples

Expand All @@ -98,6 +138,38 @@ See the `examples` directory for more detailed usage examples:
cargo run --example basic
```

### Concurrent Usage Example

```rust
use rollkit_client::{RollkitClient, StoreClient};
use tokio::task;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = RollkitClient::connect("http://localhost:50051").await?;
let store = StoreClient::new(&client);

// Service clients can be used concurrently
let mut handles = vec![];

for height in 0..10 {
let store_clone = store.clone();
let handle = task::spawn(async move {
store_clone.get_block_by_height(height).await
});
handles.push(handle);
}

// Wait for all tasks to complete
for handle in handles {
let result = handle.await??;
println!("Got block: {:?}", result);
}

Ok(())
}
```

## Error Handling

All methods return `Result<T, RollkitClientError>` where `RollkitClientError` encompasses:
Expand Down
28 changes: 4 additions & 24 deletions client/crates/rollkit-client/examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rollkit_client::{HealthClient, P2PClient, RollkitClient, SignerClient, StoreClient};
use rollkit_client::{HealthClient, P2PClient, RollkitClient, StoreClient};
use std::error::Error;

#[tokio::main]
Expand All @@ -16,7 +16,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

// Check health status
println!("\n=== Health Check ===");
let mut health = HealthClient::new(&client);
let health = HealthClient::new(&client);
match health.get_health().await {
Ok(health_response) => {
println!("Health status: {:?}", health_response.status());
Expand All @@ -27,7 +27,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

// Get P2P information
println!("\n=== P2P Information ===");
let mut p2p = P2PClient::new(&client);
let p2p = P2PClient::new(&client);
match p2p.get_net_info().await {
Ok(net_info) => {
println!("Network ID: {}", net_info.id);
Expand All @@ -47,29 +47,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
Err(e) => println!("Failed to get peer info: {e}"),
}

// Get signer information
println!("\n=== Signer Information ===");
let mut signer = SignerClient::new(&client);
match signer.get_public_key().await {
Ok(pubkey) => {
println!("Public key (hex): {}", hex::encode(&pubkey));
}
Err(e) => println!("Failed to get public key: {e}"),
}

// Example: Sign a message
let message = b"Hello, Rollkit!";
match signer.sign(message.to_vec()).await {
Ok(signature) => {
println!("Message signed successfully");
println!("Signature (hex): {}", hex::encode(&signature));
}
Err(e) => println!("Failed to sign message: {e}"),
}

// Get store information
println!("\n=== Store Information ===");
let mut store = StoreClient::new(&client);
let store = StoreClient::new(&client);

// Try to get the latest block (height 0 for genesis)
match store.get_block_by_height(0).await {
Expand Down
95 changes: 94 additions & 1 deletion client/crates/rollkit-client/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use crate::error::{Result, RollkitClientError};
use std::time::Duration;
use tonic::transport::{Channel, Endpoint};
use tonic::transport::{Channel, ClientTlsConfig, Endpoint};

#[derive(Clone, Debug)]
pub struct RollkitClient {
channel: Channel,
endpoint: String,
}

/// Builder for configuring a RollkitClient
#[derive(Debug)]
pub struct RollkitClientBuilder {
endpoint: String,
timeout: Option<Duration>,
connect_timeout: Option<Duration>,
tls_config: Option<ClientTlsConfig>,
}

impl RollkitClient {
/// Create a new RollkitClient with the given endpoint
pub async fn connect(endpoint: impl Into<String>) -> Result<Self> {
Expand All @@ -17,6 +26,16 @@ impl RollkitClient {
Ok(Self { channel, endpoint })
}

/// Create a new RollkitClient builder
pub fn builder() -> RollkitClientBuilder {
RollkitClientBuilder {
endpoint: String::new(),
timeout: None,
connect_timeout: None,
tls_config: None,
}
}

/// Create a new RollkitClient with custom channel configuration
pub async fn connect_with_config<F>(endpoint: impl Into<String>, config: F) -> Result<Self>
where
Expand Down Expand Up @@ -62,3 +81,77 @@ impl RollkitClient {
Ok(channel)
}
}

impl RollkitClientBuilder {
/// Set the endpoint URL
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}

/// Set the request timeout
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}

/// Set the connection timeout
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = Some(timeout);
self
}

/// Enable TLS with default configuration
pub fn tls(mut self) -> Self {
self.tls_config = Some(ClientTlsConfig::new());
self
}

/// Set custom TLS configuration
pub fn tls_config(mut self, config: ClientTlsConfig) -> Self {
self.tls_config = Some(config);
self
}

/// Build the RollkitClient
pub async fn build(self) -> Result<RollkitClient> {
if self.endpoint.is_empty() {
return Err(RollkitClientError::InvalidEndpoint(
"Endpoint cannot be empty".to_string(),
));
}

let endpoint = Endpoint::from_shared(self.endpoint.clone())
.map_err(|e| RollkitClientError::InvalidEndpoint(e.to_string()))?;

// Apply timeout configurations
let endpoint = if let Some(timeout) = self.timeout {
endpoint.timeout(timeout)
} else {
endpoint.timeout(Duration::from_secs(10))
};

let endpoint = if let Some(connect_timeout) = self.connect_timeout {
endpoint.connect_timeout(connect_timeout)
} else {
endpoint.connect_timeout(Duration::from_secs(5))
};

// Apply TLS configuration if provided
let endpoint = if let Some(tls_config) = self.tls_config {
endpoint.tls_config(tls_config)?
} else {
endpoint
};

let channel = endpoint
.connect()
.await
.map_err(RollkitClientError::Transport)?;

Ok(RollkitClient {
channel,
endpoint: self.endpoint,
})
}
}
10 changes: 5 additions & 5 deletions client/crates/rollkit-client/src/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ impl HealthClient {
}

/// Check if the node is alive and get its health status
pub async fn livez(&mut self) -> Result<HealthStatus> {
pub async fn livez(&self) -> Result<HealthStatus> {
let request = Request::new(());
let response = self.inner.livez(request).await?;
let response = self.inner.clone().livez(request).await?;

Ok(response.into_inner().status())
}

/// Get the full health response
pub async fn get_health(&mut self) -> Result<GetHealthResponse> {
pub async fn get_health(&self) -> Result<GetHealthResponse> {
let request = Request::new(());
let response = self.inner.livez(request).await?;
let response = self.inner.clone().livez(request).await?;

Ok(response.into_inner())
}

/// Check if the node is healthy (status is PASS)
pub async fn is_healthy(&mut self) -> Result<bool> {
pub async fn is_healthy(&self) -> Result<bool> {
let status = self.livez().await?;
Ok(status == HealthStatus::Pass)
}
Expand Down
Loading
Loading