iota_bridge/
metered_eth_provider.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt::Debug, sync::Arc};
6
7use ethers::providers::{Http, HttpClientError, JsonRpcClient, Provider};
8use serde::{Serialize, de::DeserializeOwned};
9use url::{ParseError, Url};
10
11use crate::metrics::BridgeMetrics;
12
13#[derive(Debug, Clone)]
14pub struct MeteredEthHttpProvider {
15    inner: Http,
16    metrics: Arc<BridgeMetrics>,
17}
18
19#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
20#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
21impl JsonRpcClient for MeteredEthHttpProvider {
22    type Error = HttpClientError;
23
24    async fn request<T: Serialize + Send + Sync + Debug, R: DeserializeOwned + Send>(
25        &self,
26        method: &str,
27        params: T,
28    ) -> Result<R, HttpClientError> {
29        self.metrics
30            .eth_rpc_queries
31            .with_label_values(&[method])
32            .inc();
33        let _guard = self
34            .metrics
35            .eth_rpc_queries_latency
36            .with_label_values(&[method])
37            .start_timer();
38        self.inner.request(method, params).await
39    }
40}
41
42impl MeteredEthHttpProvider {
43    pub fn new(url: impl Into<Url>, metrics: Arc<BridgeMetrics>) -> Self {
44        let inner = Http::new(url);
45        Self { inner, metrics }
46    }
47}
48
49pub fn new_metered_eth_provider(
50    url: &str,
51    metrics: Arc<BridgeMetrics>,
52) -> Result<Provider<MeteredEthHttpProvider>, ParseError> {
53    let http_provider = MeteredEthHttpProvider::new(Url::parse(url)?, metrics);
54    Ok(Provider::new(http_provider))
55}
56
57#[cfg(test)]
58mod tests {
59    use ethers::providers::Middleware;
60    use prometheus::Registry;
61
62    use super::*;
63
64    #[tokio::test]
65    #[ignore = "https://github.com/iotaledger/iota/issues/3224"]
66    async fn test_metered_eth_provider() {
67        let metrics = Arc::new(BridgeMetrics::new(&Registry::new()));
68        let provider = new_metered_eth_provider("http://localhost:9876", metrics.clone()).unwrap();
69
70        assert_eq!(
71            metrics
72                .eth_rpc_queries
73                .get_metric_with_label_values(&["eth_blockNumber"])
74                .unwrap()
75                .get(),
76            0
77        );
78        assert_eq!(
79            metrics
80                .eth_rpc_queries_latency
81                .get_metric_with_label_values(&["eth_blockNumber"])
82                .unwrap()
83                .get_sample_count(),
84            0
85        );
86
87        provider.get_block_number().await.unwrap_err(); // the rpc cal will fail but we don't care
88
89        assert_eq!(
90            metrics
91                .eth_rpc_queries
92                .get_metric_with_label_values(&["eth_blockNumber"])
93                .unwrap()
94                .get(),
95            1
96        );
97        assert_eq!(
98            metrics
99                .eth_rpc_queries_latency
100                .get_metric_with_label_values(&["eth_blockNumber"])
101                .unwrap()
102                .get_sample_count(),
103            1
104        );
105    }
106}