Skip to content

Commit 8514a6c

Browse files
authored
feat: aleo proving service (#7656)
1 parent f50feaa commit 8514a6c

7 files changed

Lines changed: 196 additions & 45 deletions

File tree

rust/main/chains/hyperlane-aleo/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ pub enum HyperlaneAleoError {
4949
/// TryFromSliceError
5050
#[error("{0}")]
5151
TryFromSliceError(#[from] std::array::TryFromSliceError),
52+
/// Missing Auth Header
53+
#[error("Missing Auth Header")]
54+
MissingAuthHeader,
5255
/// Malicious Program Detected
5356
#[error("Malicious Program Detected: program_id={program_id}, transition={transition}")]
5457
MaliciousProgramDetected {

rust/main/chains/hyperlane-aleo/src/provider/aleo.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@ use hyperlane_core::{
3131
use hyperlane_metric::prometheus_metric::PrometheusClientMetrics;
3232

3333
use crate::{
34-
provider::{fallback::FallbackHttpClient, HttpClient, ProvingClient, RpcClient},
34+
provider::{
35+
fallback::FallbackHttpClient, AleoClient, BaseHttpClient, JWTBaseHttpClient, ProvingClient,
36+
RpcClient,
37+
},
3538
utils::{get_tx_id, to_h256},
3639
AleoSigner, ConnectionConf, CurrentNetwork, FeeEstimate, HyperlaneAleoError,
3740
};
3841

39-
/// Aleo Http Client trait alias
40-
pub trait AleoClient: HttpClient + Clone + Debug + Send + Sync + 'static {}
41-
impl<T> AleoClient for T where T: HttpClient + Clone + Debug + Send + Sync + 'static {}
42-
4342
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
4443
struct FeeEstimateCacheKey {
4544
program_id: String,
@@ -53,7 +52,7 @@ pub struct AleoProvider<C: AleoClient = FallbackHttpClient> {
5352
client: RpcClient<C>,
5453
domain: HyperlaneDomain,
5554
network: u16,
56-
proving_service: Option<ProvingClient<C>>,
55+
proving_service: Option<ProvingClient<FallbackHttpClient<JWTBaseHttpClient>>>,
5756
signer: Option<AleoSigner>,
5857
priority_fee_multiplier: f64,
5958
estimate_cache: Arc<RwLock<HashMap<FeeEstimateCacheKey, FeeEstimate>>>,
@@ -98,7 +97,7 @@ impl AleoProvider<FallbackHttpClient> {
9897
chain: Option<hyperlane_metric::prometheus_metric::ChainInfo>,
9998
) -> ChainResult<Self> {
10099
let proving_service = if !conf.proving_service.is_empty() {
101-
let client = FallbackHttpClient::new(
100+
let client = FallbackHttpClient::new::<JWTBaseHttpClient>(
102101
conf.proving_service.clone(),
103102
metrics.clone(),
104103
chain.clone(),
@@ -110,7 +109,7 @@ impl AleoProvider<FallbackHttpClient> {
110109
};
111110

112111
Ok(Self {
113-
client: RpcClient::new(FallbackHttpClient::new(
112+
client: RpcClient::new(FallbackHttpClient::new::<BaseHttpClient>(
114113
conf.rpcs.clone(),
115114
metrics,
116115
chain,

rust/main/chains/hyperlane-aleo/src/provider/base.rs

Lines changed: 139 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
use std::{
2+
sync::Arc,
3+
time::{Duration, Instant},
4+
};
5+
16
use async_trait::async_trait;
7+
use reqwest::header::{HeaderValue, AUTHORIZATION};
28
use reqwest::Client as ReqestClient;
39
use reqwest_utils::parse_custom_rpc_headers;
410
use serde::de::DeserializeOwned;
11+
use tokio::sync::RwLock;
12+
use url::Url;
513

614
use hyperlane_core::{ChainCommunicationError, ChainResult};
7-
use url::Url;
815

9-
use crate::provider::HttpClient;
16+
use crate::provider::{HttpClient, HttpClientBuilder};
1017
use crate::HyperlaneAleoError;
11-
use std::time::Duration;
1218

1319
// Default timeouts
1420
pub const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -34,52 +40,156 @@ impl BaseHttpClient {
3440
let suffix = match network {
3541
0 => "mainnet",
3642
1 => "testnet",
43+
2 => "canary",
3744
id => return Err(HyperlaneAleoError::UnknownNetwork(id).into()),
3845
};
3946
Ok(Self {
4047
client,
4148
base_url: url.to_string().trim_end_matches("/").to_string() + "/" + suffix,
4249
})
4350
}
51+
}
52+
53+
#[async_trait]
54+
impl HttpClient for BaseHttpClient {
55+
/// Makes a GET request to the API
56+
async fn request<T: DeserializeOwned + Send>(
57+
&self,
58+
path: &str,
59+
query: impl Into<Option<serde_json::Value>> + Send,
60+
) -> ChainResult<T> {
61+
let url = format!("{}/{}", self.base_url, path);
62+
let query: serde_json::Value = query.into().unwrap_or_default();
63+
let response = self
64+
.client
65+
.get(&url)
66+
.query(&query)
67+
.send()
68+
.await
69+
.map_err(HyperlaneAleoError::from)?;
70+
let response = response
71+
.error_for_status()
72+
.map_err(HyperlaneAleoError::from)?;
73+
let json = response.json().await.map_err(HyperlaneAleoError::from)?;
74+
Ok(json)
75+
}
4476

45-
pub fn with_timeouts(
46-
base_url: impl Into<String>,
47-
connect_timeout: Duration,
48-
request_timeout: Duration,
49-
) -> Result<Self, HyperlaneAleoError> {
77+
/// Makes a POST request to the API
78+
async fn request_post<T: DeserializeOwned + Send>(
79+
&self,
80+
path: &str,
81+
body: &serde_json::Value,
82+
) -> ChainResult<T> {
83+
let url = format!("{}/{}", self.base_url, path);
84+
let response = self
85+
.client
86+
.post(&url)
87+
.json(body)
88+
.send()
89+
.await
90+
.map_err(HyperlaneAleoError::from)?;
91+
let response = response
92+
.error_for_status()
93+
.map_err(HyperlaneAleoError::from)?;
94+
Ok(response.json().await.map_err(HyperlaneAleoError::from)?)
95+
}
96+
}
97+
98+
impl HttpClientBuilder for BaseHttpClient {
99+
type Client = BaseHttpClient;
100+
101+
fn build(url: Url, network: u16) -> ChainResult<Self::Client> {
102+
BaseHttpClient::new(url, network)
103+
}
104+
}
105+
106+
/// Base Http client that performs REST-ful queries
107+
#[derive(Clone, Debug)]
108+
pub struct JWTBaseHttpClient {
109+
client: ReqestClient,
110+
base_url: String,
111+
suffix: String,
112+
auth_url: String,
113+
auth_token: Arc<RwLock<Option<(HeaderValue, Instant)>>>,
114+
}
115+
116+
impl JWTBaseHttpClient {
117+
/// Creates a new Http client
118+
pub fn new(base_url: Url, network: u16) -> ChainResult<Self> {
119+
let (headers, url) =
120+
parse_custom_rpc_headers(&base_url).map_err(ChainCommunicationError::from_other)?;
121+
let auth_url = headers
122+
.get("x-auth-url")
123+
.and_then(|v| v.to_str().ok())
124+
.unwrap_or_default()
125+
.to_string();
50126
let client = ReqestClient::builder()
51-
.connect_timeout(connect_timeout)
52-
.timeout(request_timeout)
127+
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
128+
.timeout(DEFAULT_REQUEST_TIMEOUT)
129+
.default_headers(headers)
53130
.build()
54131
.map_err(HyperlaneAleoError::from)?;
132+
let suffix = match network {
133+
0 => "mainnet",
134+
1 => "testnet",
135+
2 => "canary",
136+
id => return Err(HyperlaneAleoError::UnknownNetwork(id).into()),
137+
};
55138
Ok(Self {
56139
client,
57-
base_url: base_url.into(),
140+
base_url: url.to_string().trim_end_matches("/").to_string(),
141+
auth_token: Default::default(),
142+
suffix: suffix.to_string(),
143+
auth_url,
58144
})
59145
}
60146

61-
pub fn client(&self) -> &ReqestClient {
62-
&self.client
63-
}
147+
/// Gets the authentication token if it is still valid
148+
pub async fn get_auth_token(&self) -> ChainResult<HeaderValue> {
149+
{
150+
let auth_token = self.auth_token.read().await;
151+
if let Some((token, expires_at)) = &*auth_token {
152+
if Instant::now() < *expires_at {
153+
return Ok(token.clone());
154+
}
155+
}
156+
}
64157

65-
pub fn base_url(&self) -> &str {
66-
&self.base_url
158+
let response = self
159+
.client
160+
.post(&self.auth_url)
161+
.send()
162+
.await
163+
.map_err(HyperlaneAleoError::from)?;
164+
let result = response
165+
.headers()
166+
.get(AUTHORIZATION)
167+
.ok_or(HyperlaneAleoError::MissingAuthHeader)?
168+
.clone();
169+
let expires = Instant::now()
170+
.checked_add(Duration::from_secs(60 * 15))
171+
.unwrap_or(Instant::now()); // Tokens last 15 minutes
172+
let mut auth_token = self.auth_token.write().await;
173+
*auth_token = Some((result.clone(), expires));
174+
Ok(result.clone())
67175
}
68176
}
69177

70178
#[async_trait]
71-
impl HttpClient for BaseHttpClient {
179+
impl HttpClient for JWTBaseHttpClient {
72180
/// Makes a GET request to the API
73181
async fn request<T: DeserializeOwned + Send>(
74182
&self,
75183
path: &str,
76184
query: impl Into<Option<serde_json::Value>> + Send,
77185
) -> ChainResult<T> {
78-
let url = format!("{}/{}", self.base_url, path);
186+
let url = format!("{}/{}/{}", self.base_url, self.suffix, path);
79187
let query: serde_json::Value = query.into().unwrap_or_default();
188+
let auth = self.get_auth_token().await?;
80189
let response = self
81190
.client
82191
.get(&url)
192+
.header(AUTHORIZATION, auth)
83193
.query(&query)
84194
.send()
85195
.await
@@ -97,10 +207,12 @@ impl HttpClient for BaseHttpClient {
97207
path: &str,
98208
body: &serde_json::Value,
99209
) -> ChainResult<T> {
100-
let url = format!("{}/{}", self.base_url, path);
210+
let url = format!("{}/{}/{}", self.base_url, self.suffix, path);
211+
let auth = self.get_auth_token().await?;
101212
let response = self
102213
.client
103214
.post(&url)
215+
.header(AUTHORIZATION, auth)
104216
.json(body)
105217
.send()
106218
.await
@@ -111,3 +223,11 @@ impl HttpClient for BaseHttpClient {
111223
Ok(response.json().await.map_err(HyperlaneAleoError::from)?)
112224
}
113225
}
226+
227+
impl HttpClientBuilder for JWTBaseHttpClient {
228+
type Client = JWTBaseHttpClient;
229+
230+
fn build(url: Url, network: u16) -> ChainResult<Self::Client> {
231+
JWTBaseHttpClient::new(url, network)
232+
}
233+
}

rust/main/chains/hyperlane-aleo/src/provider/fallback.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ use hyperlane_metric::prometheus_metric::{
1010
use snarkvm_console_account::{DeserializeOwned, Itertools};
1111
use url::Url;
1212

13-
use crate::provider::{metric::MetricHttpClient, HttpClient, RpcClient};
13+
use crate::provider::{
14+
metric::MetricHttpClient, AleoClient, BaseHttpClient, HttpClient, HttpClientBuilder, RpcClient,
15+
};
1416

1517
/// Fallback Http Client that tries multiple RpcClients in order
1618
#[derive(Clone, Debug)]
17-
pub struct FallbackHttpClient {
18-
fallback: FallbackProvider<RpcClient<MetricHttpClient>, RpcClient<MetricHttpClient>>,
19+
pub struct FallbackHttpClient<C: AleoClient = BaseHttpClient> {
20+
fallback: FallbackProvider<RpcClient<MetricHttpClient<C>>, RpcClient<MetricHttpClient<C>>>,
1921
}
2022

21-
impl FallbackHttpClient {
23+
impl<C: AleoClient> FallbackHttpClient<C> {
2224
/// Creates a new FallbackHttpClient from a list of base urls
23-
pub fn new(
25+
pub fn new<Builder: HttpClientBuilder<Client = C>>(
2426
urls: Vec<Url>,
2527
metrics: PrometheusClientMetrics,
2628
chain: Option<hyperlane_metric::prometheus_metric::ChainInfo>,
@@ -31,7 +33,7 @@ impl FallbackHttpClient {
3133
.map(|url| {
3234
let metrics_config =
3335
PrometheusConfig::from_url(&url, ClientConnectionType::Rpc, chain.clone());
34-
MetricHttpClient::new(url, metrics.clone(), metrics_config, network)
36+
MetricHttpClient::new::<Builder>(url, metrics.clone(), metrics_config, network)
3537
})
3638
.collect::<ChainResult<Vec<_>>>()?
3739
.into_iter()
@@ -51,7 +53,7 @@ impl<C: HttpClient + std::fmt::Debug + Send + Sync> BlockNumberGetter for RpcCli
5153
}
5254

5355
#[async_trait]
54-
impl HttpClient for FallbackHttpClient {
56+
impl<C: AleoClient> HttpClient for FallbackHttpClient<C> {
5557
/// Makes a GET request to the API
5658
async fn request<T: DeserializeOwned + Send>(
5759
&self,

rust/main/chains/hyperlane-aleo/src/provider/metric.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::time::Instant;
1+
use std::{ops::Deref, time::Instant};
22

33
use async_trait::async_trait;
44
use snarkvm_console_account::DeserializeOwned;
@@ -7,25 +7,33 @@ use url::Url;
77
use hyperlane_core::ChainResult;
88
use hyperlane_metric::prometheus_metric::{PrometheusClientMetrics, PrometheusConfig};
99

10-
use crate::provider::{BaseHttpClient, HttpClient, RpcClient};
10+
use crate::provider::{AleoClient, BaseHttpClient, HttpClient, HttpClientBuilder, RpcClient};
1111

1212
/// Fallback Http Client that tries multiple RpcClients in order
1313
#[derive(Debug)]
14-
pub struct MetricHttpClient {
15-
inner: RpcClient<BaseHttpClient>,
14+
pub struct MetricHttpClient<C: AleoClient = BaseHttpClient> {
15+
inner: RpcClient<C>,
1616
metrics: PrometheusClientMetrics,
1717
metrics_config: PrometheusConfig,
1818
}
1919

20-
impl Drop for MetricHttpClient {
20+
impl<C: AleoClient> Deref for MetricHttpClient<C> {
21+
type Target = RpcClient<C>;
22+
23+
fn deref(&self) -> &Self::Target {
24+
&self.inner
25+
}
26+
}
27+
28+
impl<C: AleoClient> Drop for MetricHttpClient<C> {
2129
fn drop(&mut self) {
2230
// decrement provider metric count
2331
let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain);
2432
self.metrics.decrement_provider_instance(chain_name);
2533
}
2634
}
2735

28-
impl Clone for MetricHttpClient {
36+
impl<C: AleoClient> Clone for MetricHttpClient<C> {
2937
fn clone(&self) -> Self {
3038
// increment provider metric count
3139
let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain);
@@ -39,9 +47,9 @@ impl Clone for MetricHttpClient {
3947
}
4048
}
4149

42-
impl MetricHttpClient {
43-
/// Creates a new FallbackHttpClient from a list of base urls
44-
pub fn new(
50+
impl<C: AleoClient> MetricHttpClient<C> {
51+
/// Creates a new MetricHttpClient
52+
pub fn new<Builder: HttpClientBuilder<Client = C>>(
4553
url: Url,
4654
metrics: PrometheusClientMetrics,
4755
metrics_config: PrometheusConfig,
@@ -51,7 +59,7 @@ impl MetricHttpClient {
5159
let chain_name = PrometheusConfig::chain_name(&metrics_config.chain);
5260
metrics.increment_provider_instance(chain_name);
5361

54-
let base_client = BaseHttpClient::new(url, network)?;
62+
let base_client = Builder::build(url, network)?;
5563
Ok(Self {
5664
inner: RpcClient::new(base_client),
5765
metrics,
@@ -61,7 +69,7 @@ impl MetricHttpClient {
6169
}
6270

6371
#[async_trait]
64-
impl HttpClient for MetricHttpClient {
72+
impl<C: AleoClient> HttpClient for MetricHttpClient<C> {
6573
/// Makes a GET request to the API
6674
async fn request<T: DeserializeOwned + Send>(
6775
&self,

0 commit comments

Comments
 (0)