diff --git a/gcli/src/app.rs b/gcli/src/app.rs index b425886ec26..7d2370ca933 100644 --- a/gcli/src/app.rs +++ b/gcli/src/app.rs @@ -110,8 +110,9 @@ pub trait App: Parser + Sync { /// Get gear api without signing in with password. async fn api(&self) -> anyhow::Result { 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) @@ -119,11 +120,12 @@ pub trait App: Parser + Sync { /// Get signer. async fn signer(&self) -> anyhow::Result { - 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())?; diff --git a/gclient/src/api/mod.rs b/gclient/src/api/mod.rs index 6d1f2e3fbba..0fd2c029cb0 100644 --- a/gclient/src/api/mod.rs +++ b/gclient/src/api/mod.rs @@ -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}; @@ -38,6 +38,11 @@ use std::{ffi::OsStr, sync::Arc}; pub struct GearApi(Signer, Option>); 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 { @@ -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) -> Result { - 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`. @@ -201,3 +196,51 @@ impl From 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) -> Self { + self.suri = suri.as_ref().into(); + self + } + + /// Build gear api + pub async fn build(self, address: WSAddress) -> Result { + 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(), + } + } +} diff --git a/gsdk/src/api.rs b/gsdk/src/api.rs index 1989753561a..f33828e5044 100644 --- a/gsdk/src/api.rs +++ b/gsdk/src/api.rs @@ -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, /// Gear RPC client @@ -36,8 +39,13 @@ pub struct Api { impl Api { /// Create new API client. - pub async fn new(url: impl Into>) -> Result { - Self::new_with_timeout(url.into(), None).await + pub async fn new(uri: impl Into>) -> Result { + Self::builder().build(uri).await + } + + /// Resolve api builder + pub fn builder() -> ApiBuilder { + ApiBuilder::default() } /// Gear RPC Client @@ -45,27 +53,6 @@ impl Api { self.rpc.clone() } - /// Create new API client with timeout. - pub async fn new_with_timeout( - url: impl Into>, - timeout: impl Into>, - ) -> Result { - 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 /// /// @@ -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>) -> Result { + 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, + } + } +} diff --git a/gsdk/src/client.rs b/gsdk/src/client.rs index 993c7823589..ae922d2b909 100644 --- a/gsdk/src/client.rs +++ b/gsdk/src/client.rs @@ -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>); @@ -65,17 +63,10 @@ pub enum RpcClient { impl RpcClient { /// Create RPC client from url and timeout. - pub async fn new( - url: impl Into>, - timeout: impl Into>, - ) -> Result { - 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 { + log::info!("Connecting to {uri} ..."); + + if uri.starts_with("ws") { Ok(Self::Ws( WsClientBuilder::default() // Actually that stand for the response too. @@ -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 { @@ -166,17 +157,19 @@ async fn subscription_stream( pub struct Rpc { rpc: SubxtRpcClient, methods: LegacyRpcMethods, + retries: u8, } impl Rpc { /// Create RPC client from url and timeout. - pub async fn new( - url: impl Into>, - timeout: impl Into>, - ) -> Result { - let rpc = SubxtRpcClient::new(RpcClient::new(url, timeout).await?); + pub async fn new(uri: &str, timeout: u64, retries: u8) -> Result { + 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. @@ -190,7 +183,22 @@ impl Rpc { method: &str, params: RpcParams, ) -> Result { - 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()); + } } } diff --git a/gsdk/src/lib.rs b/gsdk/src/lib.rs index b8e32a68663..6e50b92c684 100644 --- a/gsdk/src/lib.rs +++ b/gsdk/src/lib.rs @@ -21,7 +21,7 @@ //! Gear api pub use crate::{ - api::Api, + api::{Api, ApiBuilder}, config::GearConfig, metadata::Event, result::{Error, Result}, diff --git a/gsdk/tests/main.rs b/gsdk/tests/main.rs index ca24dd7a05d..ab54c7e6c64 100644 --- a/gsdk/tests/main.rs +++ b/gsdk/tests/main.rs @@ -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: