Skip to content

Commit

Permalink
feat(gsdk): introduce api builder (#4225)
Browse files Browse the repository at this point in the history
  • Loading branch information
clearloop authored Sep 9, 2024
1 parent b1b3bac commit 9aada24
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 67 deletions.
12 changes: 7 additions & 5 deletions gcli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,22 @@ pub trait App: Parser + Sync {
/// Get gear api without signing in with password.
async fn api(&self) -> anyhow::Result<GearApi> {
let endpoint = self.endpoint().clone();
let timeout = self.timeout();
Api::new_with_timeout(endpoint.as_deref(), timeout)
Api::builder()
.timeout(self.timeout())
.build(endpoint.as_deref())
.await
.map(Into::into)
.map_err(Into::into)
}

/// Get signer.
async fn signer(&self) -> anyhow::Result<GearApi> {
let endpoint = self.endpoint().clone();
let timeout = self.timeout();
let passwd = self.passwd();

let api = Api::new_with_timeout(endpoint.as_deref(), timeout).await?;
let api = Api::builder()
.timeout(self.timeout())
.build(self.endpoint().as_deref())
.await?;
let pair = Keyring::load(gring::cmd::Command::store()?)?
.primary()?
.decrypt(passwd.clone().and_then(|p| hex::decode(p).ok()).as_deref())?;
Expand Down
67 changes: 55 additions & 12 deletions gclient/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use gear_node_wrapper::{Node, NodeInstance};
use gsdk::{
ext::{sp_core::sr25519, sp_runtime::AccountId32},
signer::Signer,
Api,
Api, ApiBuilder,
};
use std::{ffi::OsStr, sync::Arc};

Expand All @@ -38,6 +38,11 @@ use std::{ffi::OsStr, sync::Arc};
pub struct GearApi(Signer, Option<Arc<NodeInstance>>);

impl GearApi {
/// Create api builder
pub fn builder() -> GearApiBuilder {
GearApiBuilder::default()
}

/// Create and init a new `GearApi` specified by its `address` on behalf of
/// the default `Alice` user.
pub async fn init(address: WSAddress) -> Result<Self> {
Expand All @@ -51,17 +56,7 @@ impl GearApi {
/// etc.). The password for URI should be specified in the same `suri`,
/// separated by the `':'` char.
pub async fn init_with(address: WSAddress, suri: impl AsRef<str>) -> Result<Self> {
let mut suri = suri.as_ref().splitn(2, ':');

Api::new(address.url().as_str())
.await
.and_then(|api| {
Ok(Self(
api.signer(suri.next().expect("Infallible"), suri.next())?,
None,
))
})
.map_err(Into::into)
Self::builder().suri(suri).build(address).await
}

/// Change SURI to the provided `suri` and return `Self`.
Expand Down Expand Up @@ -201,3 +196,51 @@ impl From<GearApi> for Signer {
api.0
}
}

/// Gear API builder
pub struct GearApiBuilder {
/// gsdk api builder
inner: ApiBuilder,
/// suri for keypair
suri: String,
}

impl GearApiBuilder {
/// Set retries of rpc client
pub fn retries(mut self, retries: u8) -> Self {
self.inner = self.inner.retries(retries);
self
}

/// Set timeout of rpc client ( in milliseconds )
pub fn timeout(mut self, timeout: u64) -> Self {
self.inner = self.inner.timeout(timeout);
self
}

/// Set initial suri for keypiar
pub fn suri(mut self, suri: impl AsRef<str>) -> Self {
self.suri = suri.as_ref().into();
self
}

/// Build gear api
pub async fn build(self, address: WSAddress) -> Result<GearApi> {
let mut suri = self.suri.splitn(2, ':');
let api = self.inner.build(address.url().as_str()).await?;

Ok(GearApi(
api.signer(suri.next().expect("Infallible"), suri.next())?,
None,
))
}
}

impl Default for GearApiBuilder {
fn default() -> Self {
Self {
suri: "//Alice".into(),
inner: ApiBuilder::default(),
}
}
}
84 changes: 59 additions & 25 deletions gsdk/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ use anyhow::Result;
use core::ops::{Deref, DerefMut};
use subxt::OnlineClient;

const DEFAULT_GEAR_ENDPOINT: &str = "wss://rpc.vara.network:443";
const DEFAULT_TIMEOUT_MILLISECS: u64 = 60_000;
const DEFAULT_RETRIES: u8 = 0;

/// Gear api wrapper.
#[derive(Clone)]
pub struct Api {
/// How many times we'll retry when rpc requests failed.
pub retry: u16,
/// Substrate client
client: OnlineClient<GearConfig>,

/// Gear RPC client
Expand All @@ -36,36 +39,20 @@ pub struct Api {

impl Api {
/// Create new API client.
pub async fn new(url: impl Into<Option<&str>>) -> Result<Self> {
Self::new_with_timeout(url.into(), None).await
pub async fn new(uri: impl Into<Option<&str>>) -> Result<Self> {
Self::builder().build(uri).await
}

/// Resolve api builder
pub fn builder() -> ApiBuilder {
ApiBuilder::default()
}

/// Gear RPC Client
pub fn rpc(&self) -> Rpc {
self.rpc.clone()
}

/// Create new API client with timeout.
pub async fn new_with_timeout(
url: impl Into<Option<&str>>,
timeout: impl Into<Option<u64>>,
) -> Result<Self> {
let rpc = Rpc::new(url, timeout).await?;

Ok(Self {
// Retry our failed RPC requests for 5 times by default.
retry: 5,
client: OnlineClient::from_rpc_client(rpc.client()).await?,
rpc,
})
}

/// Setup retry times and return the API instance.
pub fn with_retry(mut self, retry: u16) -> Self {
self.retry = retry;
self
}

/// Subscribe all blocks
///
///
Expand Down Expand Up @@ -138,3 +125,50 @@ impl DerefMut for Api {
&mut self.client
}
}

/// gsdk api builder
pub struct ApiBuilder {
/// RPC retries
retries: u8,
/// RPC timeout
timeout: u64,
}

impl ApiBuilder {
/// Build api from the provided config
pub async fn build(self, uri: impl Into<Option<&str>>) -> Result<Api> {
let uri: Option<&str> = uri.into();
let rpc = Rpc::new(
uri.unwrap_or(DEFAULT_GEAR_ENDPOINT),
self.timeout,
self.retries,
)
.await?;

Ok(Api {
client: OnlineClient::from_rpc_client(rpc.client()).await?,
rpc,
})
}

/// Set rpc retries
pub fn retries(mut self, retries: u8) -> Self {
self.retries = retries;
self
}

/// Set rpc timeout in milliseconds
pub fn timeout(mut self, timeout: u64) -> Self {
self.timeout = timeout;
self
}
}

impl Default for ApiBuilder {
fn default() -> Self {
Self {
retries: DEFAULT_RETRIES,
timeout: DEFAULT_TIMEOUT_MILLISECS,
}
}
}
54 changes: 31 additions & 23 deletions gsdk/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ use subxt::{
error::RpcError,
};

const DEFAULT_GEAR_ENDPOINT: &str = "wss://rpc.vara.network:443";
const DEFAULT_TIMEOUT: u64 = 60_000;
const ONE_HUNDRED_MEGA_BYTES: u32 = 100 * 1024 * 1024;

struct Params(Option<Box<RawValue>>);
Expand All @@ -65,17 +63,10 @@ pub enum RpcClient {

impl RpcClient {
/// Create RPC client from url and timeout.
pub async fn new(
url: impl Into<Option<&str>>,
timeout: impl Into<Option<u64>>,
) -> Result<Self> {
let (url, timeout) = (
url.into().unwrap_or(DEFAULT_GEAR_ENDPOINT),
timeout.into().unwrap_or(DEFAULT_TIMEOUT),
);

log::info!("Connecting to {url} ...");
if url.starts_with("ws") {
pub async fn new(uri: &str, timeout: u64) -> Result<Self> {
log::info!("Connecting to {uri} ...");

if uri.starts_with("ws") {
Ok(Self::Ws(
WsClientBuilder::default()
// Actually that stand for the response too.
Expand All @@ -85,15 +76,15 @@ impl RpcClient {
.max_request_body_size(ONE_HUNDRED_MEGA_BYTES)
.connection_timeout(Duration::from_millis(timeout))
.request_timeout(Duration::from_millis(timeout))
.build(url)
.build(uri)
.await
.map_err(Error::SubxtRpc)?,
))
} else if url.starts_with("http") {
} else if uri.starts_with("http") {
Ok(Self::Http(
HttpClientBuilder::default()
.request_timeout(Duration::from_millis(timeout))
.build(url)
.build(uri)
.map_err(Error::SubxtRpc)?,
))
} else {
Expand Down Expand Up @@ -166,17 +157,19 @@ async fn subscription_stream<C: SubscriptionClientT>(
pub struct Rpc {
rpc: SubxtRpcClient,
methods: LegacyRpcMethods<GearConfig>,
retries: u8,
}

impl Rpc {
/// Create RPC client from url and timeout.
pub async fn new(
url: impl Into<Option<&str>>,
timeout: impl Into<Option<u64>>,
) -> Result<Self> {
let rpc = SubxtRpcClient::new(RpcClient::new(url, timeout).await?);
pub async fn new(uri: &str, timeout: u64, retries: u8) -> Result<Self> {
let rpc = SubxtRpcClient::new(RpcClient::new(uri, timeout).await?);
let methods = LegacyRpcMethods::new(rpc.clone());
Ok(Self { rpc, methods })
Ok(Self {
rpc,
methods,
retries,
})
}

/// Get RPC client.
Expand All @@ -190,7 +183,22 @@ impl Rpc {
method: &str,
params: RpcParams,
) -> Result<Res> {
self.rpc.request(method, params).await.map_err(Into::into)
let mut retries = 0;

loop {
let r = self
.rpc
.request(method, params.clone())
.await
.map_err(Into::into);

if retries == self.retries || r.is_ok() {
return r;
}

retries += 1;
log::warn!("Failed to send request: {:?}, retries: {retries}", r.err());
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion gsdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

//! Gear api
pub use crate::{
api::Api,
api::{Api, ApiBuilder},
config::GearConfig,
metadata::Event,
result::{Error, Result},
Expand Down
2 changes: 1 addition & 1 deletion gsdk/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use gsdk::Api;

#[tokio::test]
async fn timeout() {
let error = Api::new_with_timeout(None, 0).await.err();
let error = Api::builder().timeout(0).build(None).await.err();
// NOTE:
//
// There are two kinds of timeout error provided by subxt:
Expand Down

0 comments on commit 9aada24

Please sign in to comment.