eigen_client_eth/
instrumented_client.rs

1use crate::client::BackendClient;
2use alloy::consensus::TxEnvelope;
3use alloy::primitives::{Address, BlockHash, BlockNumber, Bytes, ChainId, B256, U256, U64};
4use alloy::providers::{Provider, ProviderBuilder, RootProvider};
5use alloy::pubsub::Subscription;
6use alloy::rlp::Encodable;
7use alloy::rpc::json_rpc::{RpcRecv, RpcSend};
8use alloy::rpc::types::eth::{
9    Block, BlockNumberOrTag, FeeHistory, Filter, Header, Log, SyncStatus, Transaction,
10    TransactionReceipt, TransactionRequest,
11};
12use alloy::transports::ws::WsConnect;
13use alloy::transports::{TransportError, TransportResult};
14use eigen_metrics_collectors_rpc_calls::RpcCallsMetrics as RpcCallsCollector;
15use hex;
16use std::time::Instant;
17use thiserror::Error;
18use tracing::error;
19use url::Url;
20
21const PENDING_TAG: &str = "pending";
22
23/// This struct represents an instrumented client that can be used to interact with an Ethereum node.
24/// It provides a set of methods to interact with the node and measures the duration of the calls.
25pub struct InstrumentedClient {
26    http_client: Option<RootProvider>,
27    ws_client: Option<RootProvider>,
28    rpc_collector: RpcCallsCollector,
29    net_version: u64,
30}
31
32/// Possible errors raised in signer creation
33#[derive(Error, Debug)]
34pub enum InstrumentedClientError {
35    #[error("invalid url")]
36    InvalidUrl,
37    #[error("error getting version")]
38    ErrorGettingVersion,
39    #[error("error running command")]
40    CommandError,
41}
42
43#[async_trait::async_trait]
44impl BackendClient for InstrumentedClient {
45    type Error = InstrumentedClientError;
46
47    /// Returns the latest block number.
48    ///
49    /// # Returns
50    ///
51    /// The latest block number.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if the RPC call fails.
56    async fn block_number(&self) -> Result<BlockNumber, Self::Error> {
57        self.instrument_function("eth_blockNumber", ())
58            .await
59            .inspect_err(|err| error!("Failed to get block number: {:?}", err.to_string()))
60            .map_err(|_err| InstrumentedClientError::CommandError)
61            .map(|result: U64| result.to())
62    }
63
64    /// Returns the block having the given block number.
65    ///
66    /// # Arguments
67    ///
68    /// * `number` - The block number.
69    ///
70    /// # Returns
71    ///
72    /// The block having the given block number.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if the RPC call fails.
77    async fn block_by_number(
78        &self,
79        number: BlockNumberOrTag,
80    ) -> Result<Option<Block>, Self::Error> {
81        self.instrument_function("eth_getBlockByNumber", (number, true))
82            .await
83            .inspect_err(|err| error!("Failed to get block by number: {:?}", err.to_string()))
84            .map_err(|_err| InstrumentedClientError::CommandError)
85    }
86}
87
88impl InstrumentedClient {
89    /// Creates a new instance of the InstrumentedClient.
90    ///
91    /// # Arguments
92    ///
93    /// * `url` - The URL of the RPC server.
94    ///
95    /// # Returns
96    ///
97    /// A new instance of the InstrumentedClient.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if the URL is invalid or if there is an error getting the version.
102    pub async fn new(url: &str) -> Result<Self, InstrumentedClientError> {
103        let url = Url::parse(url).map_err(|_| InstrumentedClientError::InvalidUrl)?;
104        let http_client = ProviderBuilder::new()
105            .disable_recommended_fillers()
106            .connect_http(url);
107        let net_version = http_client
108            .get_net_version()
109            .await
110            .map_err(|_| InstrumentedClientError::ErrorGettingVersion)?;
111
112        let rpc_collector = RpcCallsCollector::new();
113        Ok(InstrumentedClient {
114            http_client: Some(http_client),
115            ws_client: None,
116            rpc_collector,
117            net_version,
118        })
119    }
120
121    /// Creates a new instance of the InstrumentedClient that supports ws connection.
122    ///
123    /// # Arguments
124    ///
125    /// * `url` - The ws URL of the RPC server .
126    ///
127    /// # Returns
128    ///
129    /// A new instance of the InstrumentedClient.
130    ///
131    /// # Errors
132    ///
133    /// Returns an error if the URL is invalid or if there is an error getting the version.
134    pub async fn new_ws(url: &str) -> Result<Self, InstrumentedClientError> {
135        let url = Url::parse(url).map_err(|_| InstrumentedClientError::InvalidUrl)?;
136        let ws_connect = WsConnect::new(url);
137
138        let ws_client = ProviderBuilder::new()
139            .disable_recommended_fillers()
140            .connect_ws(ws_connect)
141            .await
142            .unwrap();
143        let net_version = ws_client
144            .get_net_version()
145            .await
146            .map_err(|_| InstrumentedClientError::ErrorGettingVersion)?;
147
148        let rpc_collector = RpcCallsCollector::new();
149        Ok(InstrumentedClient {
150            http_client: None,
151            ws_client: Some(ws_client),
152            rpc_collector,
153            net_version,
154        })
155    }
156
157    /// Creates a new instance of the InstrumentedClient from an existing client (`RootProvider`).
158    ///
159    /// # Arguments
160    ///
161    /// * `client` - The existing client (`RootProvider`).
162    ///
163    /// # Returns
164    ///
165    /// A new instance of the InstrumentedClient.
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if there is an error getting the version.
170    pub async fn new_from_client(client: RootProvider) -> Result<Self, InstrumentedClientError> {
171        let net_version = client
172            .get_net_version()
173            .await
174            .map_err(|_| InstrumentedClientError::ErrorGettingVersion)?;
175
176        let rpc_collector = RpcCallsCollector::new();
177        Ok(InstrumentedClient {
178            http_client: Some(client),
179            ws_client: None,
180            rpc_collector,
181            net_version,
182        })
183    }
184
185    /// Returns the chain ID.
186    ///
187    /// # Returns
188    ///
189    /// The chain ID.
190    ///
191    /// # Errors
192    ///
193    /// Returns an error if the RPC call fails.
194    pub async fn chain_id(&self) -> TransportResult<ChainId> {
195        self.instrument_function("eth_chainId", ())
196            .await
197            .inspect_err(|err| error!("Failed to get chain id: {:?}", err.to_string()))
198            .map(|result: U64| result.to())
199    }
200
201    /// Returns the balance of the account at the given block number.
202    ///
203    /// # Arguments
204    ///
205    /// * `account` - The account address.
206    /// * `block_number` - The block number.
207    ///
208    /// # Returns
209    ///
210    /// The balance of the account at the given block number.
211    pub async fn balance_at(
212        &self,
213        account: Address,
214        block_number: BlockNumberOrTag,
215    ) -> TransportResult<U256> {
216        self.instrument_function("eth_getBalance", (account, block_number))
217            .await
218            .inspect_err(|err| error!("Failed to get balance: {:?}", err.to_string()))
219    }
220
221    /// Returns the block having the given block hash.
222    ///
223    /// # Arguments
224    ///
225    /// * `hash` - The block hash.
226    ///
227    /// # Returns
228    ///
229    /// The block having the given block hash.
230    pub async fn block_by_hash(&self, hash: BlockHash) -> TransportResult<Option<Block>> {
231        self.instrument_function("eth_getBlockByHash", (hash, true))
232            .await
233            .inspect_err(|err| error!("Failed to get block by hash: {:?}", err.to_string()))
234    }
235
236    /// Executes a message call transaction.
237    ///
238    /// # Arguments
239    ///
240    /// * `call` - The message call to be executed
241    /// * `block_number` - The block height at which the call runs. *Note:* in case this argument is n
242    ///
243    /// # Returns
244    ///
245    /// The returned value of the executed contract.
246    pub async fn call_contract(
247        &self,
248        call: TransactionRequest,
249        block_number: BlockNumberOrTag,
250    ) -> TransportResult<Bytes> {
251        self.instrument_function("eth_call", (call, block_number))
252            .await
253            .inspect_err(|err| error!("Failed to call contract: {:?}", err.to_string()))
254    }
255
256    /// Returns the compiled bytecode of a smart contract given its address and block number.
257    ///
258    /// # Arguments
259    ///
260    /// * `address` - The address of the smart contract.
261    /// * `block_number` - The block number.
262    ///
263    /// # Returns
264    ///
265    /// The compiled bytecode of the smart contract with the given address and block number.
266    pub async fn code_at(
267        &self,
268        address: Address,
269        block_number: BlockNumberOrTag,
270    ) -> TransportResult<Bytes> {
271        self.instrument_function("eth_getCode", (address, block_number))
272            .await
273            .inspect_err(|err| error!("Failed to get code: {:?}", err.to_string()))
274    }
275
276    /// Estimates the gas needed to execute a specific transaction.
277    ///
278    /// # Arguments
279    ///
280    /// * `tx` - The transaction from which the gas consumption is estimated.
281    ///
282    /// # Returns
283    ///
284    /// The estimated gas.
285    pub async fn estimate_gas(&self, tx: TransactionRequest) -> TransportResult<u64> {
286        self.instrument_function("eth_estimateGas", (tx,))
287            .await
288            .inspect_err(|err| error!("Failed to estimate gas: {:?}", err.to_string()))
289            .map(|result: U64| result.to())
290    }
291
292    /// Returns a collection of historical gas information.
293    ///
294    /// # Arguments
295    ///
296    /// * `block_count` - The number of blocks to include in the collection.
297    /// * `last_block` - The last block number to include in the collection.
298    /// * `reward_percentiles` - A sorted list of percentage points used to
299    ///   sample the effective priority fees per gas from each block. The samples are
300    ///   taken in ascending order and weighted by gas usage. The list is sorted increasingly.
301    pub async fn fee_history(
302        &self,
303        block_count: u64,
304        last_block: BlockNumberOrTag,
305        reward_percentiles: &[f64],
306    ) -> TransportResult<FeeHistory> {
307        self.instrument_function(
308            "eth_feeHistory",
309            (block_count, last_block, reward_percentiles),
310        )
311        .await
312        .inspect_err(|err| error!("Failed to get fee history: {:?}", err.to_string()))
313    }
314    /// Executes a filter query.
315    ///
316    /// # Arguments
317    ///
318    /// * `filter` - The filter query to be executed.
319    ///
320    /// # Returns
321    ///
322    /// A vector of logs.
323    pub async fn filter_logs(&self, filter: Filter) -> TransportResult<Vec<Log>> {
324        self.instrument_function("eth_getLogs", (filter,))
325            .await
326            .inspect_err(|err| error!("Failed to get filter logs: {:?}", err.to_string()))
327    }
328
329    /// Returns the block header with the given hash.
330    ///
331    /// # Arguments
332    ///
333    /// * `hash` - The block hash.
334    ///
335    /// # Returns
336    ///
337    /// The block header.
338    pub async fn header_by_hash(&self, hash: B256) -> TransportResult<Header> {
339        let transaction_detail = false;
340        self.instrument_function("eth_getBlockByHash", (hash, transaction_detail))
341            .await
342            .inspect_err(|err| error!("Failed to get header by hash: {:?}", err.to_string()))
343    }
344
345    /// Returns a block header with the given block number.
346    ///
347    /// # Arguments
348    ///
349    /// * `block_number` - The block number.
350    ///
351    /// # Returns
352    ///
353    /// The block header.
354    pub async fn header_by_number(
355        &self,
356        block_number: BlockNumberOrTag,
357    ) -> TransportResult<Header> {
358        let transaction_detail = false;
359        self.instrument_function("eth_getBlockByNumber", (block_number, transaction_detail))
360            .await
361            .inspect_err(|err| error!("Failed to get header by number: {:?}", err.to_string()))
362    }
363
364    /// Returns the nonce of the given account.
365    ///
366    /// # Arguments
367    ///
368    /// * `account` - The address of the account.
369    /// * `block_number` - The block number from where the nonce is taken.
370    ///
371    /// # Returns
372    ///
373    /// The nonce of the account.
374    pub async fn nonce_at(
375        &self,
376        account: Address,
377        block_number: BlockNumberOrTag,
378    ) -> TransportResult<u64> {
379        self.instrument_function("eth_getTransactionCount", (account, block_number))
380            .await
381            .inspect_err(|err| error!("Failed to get nonce: {:?}", err.to_string()))
382            .map(|result: U64| result.to())
383    }
384
385    /// Returns the wei balance of the given account in the pending state.
386    ///
387    /// # Arguments
388    ///
389    /// * `account` - The address of the account.
390    ///
391    /// # Returns
392    ///
393    /// The wei balance of the account.
394    pub async fn pending_balance_at(&self, account: Address) -> TransportResult<U256> {
395        self.instrument_function("eth_getBalance", (account, PENDING_TAG))
396            .await
397            .inspect_err(|err| error!("Failed to get pending balance: {:?}", err.to_string()))
398    }
399
400    /// Executes a message call transaction using the EVM.
401    /// The state seen by the contract call is the pending state.
402    ///
403    /// # Arguments
404    ///
405    /// * `call` - The message call to be executed
406    ///
407    /// # Returns
408    ///
409    /// The returned value of the executed contract.
410    pub async fn pending_call_contract(&self, call: TransactionRequest) -> TransportResult<Bytes> {
411        self.call_contract(call, BlockNumberOrTag::Pending).await
412    }
413
414    /// Returns the contract code of the given account in the pending state.
415    ///
416    /// # Arguments
417    ///
418    /// * `account` - The address of the contract.
419    ///
420    /// # Returns
421    ///
422    /// The contract code.
423    pub async fn pending_code_at(&self, account: Address) -> TransportResult<Bytes> {
424        self.instrument_function("eth_getCode", (account, PENDING_TAG))
425            .await
426            .inspect_err(|err| error!("Failed to get pending code: {:?}", err.to_string()))
427    }
428
429    /// Returns the account nonce of the given account in the pending state.
430    /// This is the nonce that should be used for the next transaction.
431    ///
432    /// # Arguments
433    ///
434    /// * `account` - The address of the account.
435    ///
436    /// # Returns
437    ///
438    /// * The nonce of the account in the pending state.
439    pub async fn pending_nonce_at(&self, account: Address) -> TransportResult<u64> {
440        self.instrument_function("eth_getTransactionCount", (account, PENDING_TAG))
441            .await
442            .inspect_err(|err| error!("Failed to get pending nonce: {:?}", err.to_string()))
443            .map(|result: U64| result.to())
444    }
445
446    /// Returns the value of key in the contract storage of the given account in the pending state.
447    ///
448    /// # Arguments
449    ///
450    /// * `account` - The address of the contract.
451    /// * `key` - The position in the storage.
452    ///
453    /// # Returns
454    ///
455    /// The value of the storage position at the provided address.
456    pub async fn pending_storage_at(&self, account: Address, key: U256) -> TransportResult<U256> {
457        self.instrument_function("eth_getStorageAt", (account, key, PENDING_TAG))
458            .await
459            .inspect_err(|err| error!("Failed to get pending storage: {:?}", err.to_string()))
460    }
461
462    /// Returns the total number of transactions in the pending state.
463    ///
464    /// # Returns
465    ///
466    /// The number of pending transactions.
467    pub async fn pending_transaction_count(&self) -> TransportResult<u64> {
468        self.instrument_function("eth_getBlockTransactionCountByNumber", (PENDING_TAG,))
469            .await
470            .inspect_err(|err| error!("Failed to get transaction count: {:?}", err.to_string()))
471            .map(|result: U64| result.to())
472    }
473
474    /// Sends a signed transaction into the pending pool for execution.
475    ///
476    /// # Arguments
477    ///
478    /// * `tx` - The transaction to be executed.
479    ///
480    /// # Returns
481    ///
482    /// The hash of the given transaction.
483    pub async fn send_transaction(&self, tx: TxEnvelope) -> TransportResult<B256> {
484        let mut encoded_tx = Vec::new();
485        tx.encode(&mut encoded_tx);
486        self.instrument_function("eth_sendRawTransaction", (hex::encode(encoded_tx),))
487            .await
488            .inspect_err(|err| error!("Failed to send transaction: {:?}", err.to_string()))
489    }
490
491    /// Returns the value of key in the contract storage of the given account.
492    ///
493    /// # Arguments
494    ///
495    /// * `account` - The address of the contract.
496    /// * `key` - The position in the storage.
497    /// * `block_number` - The block number from which the storage is taken.
498    ///
499    /// # Returns
500    ///
501    /// The value of the storage position at the provided address.
502    pub async fn storage_at(
503        &self,
504        account: Address,
505        key: U256,
506        block_number: U256,
507    ) -> TransportResult<U256> {
508        self.instrument_function("eth_getStorageAt", (account, key, block_number))
509            .await
510            .inspect_err(|err| error!("Failed to get storage: {:?}", err.to_string()))
511    }
512
513    /// Subscribes to the results of a streaming filter query.
514    /// *Note:* this method fails if the InstrumentedClient does not use a web socket client.
515    /// # Arguments
516    ///
517    /// * `filter` - A filter query.
518    ///
519    /// # Returns
520    ///
521    /// The subscription.
522    ///
523    /// # Errors
524    ///
525    /// * If ws_client is `None`.
526    pub async fn subscribe_filter_logs<R: RpcRecv>(
527        &self,
528        filter: Filter,
529    ) -> TransportResult<Subscription<R>> {
530        let id: U256 = self
531            .instrument_function("eth_subscribe", ("logs", filter))
532            .await
533            .inspect_err(|err| {
534                error!("Failed to get logs subscription id: {:?}", err.to_string())
535            })?;
536        if let Some(ws_client) = self.ws_client.as_ref() {
537            ws_client.get_subscription(id.into()).await
538        } else {
539            Err(TransportError::UnsupportedFeature(
540                "http client does not support eth_subscribe calls.",
541            ))
542        }
543    }
544
545    /// Subscribes to notifications about the current blockchain head.
546    /// *Note:* this method fails if the InstrumentedClient does not use a web socket client.
547    ///
548    /// # Returns
549    ///
550    /// The subscription.
551    ///
552    /// # Errors
553    ///
554    /// * If ws_client is `None`.
555    pub async fn subscribe_new_head<R: RpcRecv>(&self) -> TransportResult<Subscription<R>> {
556        let id: U256 = self
557            .instrument_function("eth_subscribe", ("newHeads",))
558            .await
559            .inspect_err(|err| error!("Failed to subscribe new head: {:?}", err.to_string()))?;
560        if let Some(ws_client) = self.ws_client.as_ref() {
561            ws_client.get_subscription(id.into()).await
562        } else {
563            Err(TransportError::UnsupportedFeature(
564                "http client does not support eth_subscribe calls.",
565            ))
566        }
567    }
568
569    /// Retrieves the currently suggested gas price.
570    ///
571    /// # Returns
572    ///
573    /// The currently suggested gas price.
574    pub async fn suggest_gas_price(&self) -> TransportResult<u64> {
575        self.instrument_function("eth_gasPrice", ())
576            .await
577            .inspect_err(|err| error!("Failed to suggest gas price: {:?}", err.to_string()))
578            .map(|result: U64| result.to())
579    }
580
581    /// Retrieves the currently suggested gas tip cap after EIP1559.
582    ///
583    /// # Returns
584    ///
585    /// The currently suggested gas price.
586    pub async fn suggest_gas_tip_cap(&self) -> TransportResult<u64> {
587        self.instrument_function("eth_maxPriorityFeePerGas", ())
588            .await
589            .inspect_err(|err| error!("Failed to suggest gas tip cap: {:?}", err.to_string()))
590            .map(|result: U64| result.to())
591    }
592
593    /// Retrieves the current progress of the sync algorithm.
594    /// If there's no sync currently running, it returns None.
595    ///
596    /// # Returns
597    ///
598    /// The current progress of the sync algorithm.
599    pub async fn sync_progress(&self) -> TransportResult<SyncStatus> {
600        self.instrument_function("eth_syncing", ())
601            .await
602            .inspect_err(|err| error!("Failed to get sync progress: {:?}", err.to_string()))
603    }
604
605    /// Returns the transaction with the given hash.
606    ///
607    /// # Arguments
608    ///
609    /// * `tx_hash` - The transaction hash.
610    ///
611    /// # Returns
612    ///
613    /// The transaction with the given hash.
614    pub async fn transaction_by_hash(&self, tx_hash: B256) -> TransportResult<Transaction> {
615        self.instrument_function("eth_getTransactionByHash", (tx_hash,))
616            .await
617            .inspect_err(|err| error!("Failed to get transaction by hash: {:?}", err.to_string()))
618    }
619
620    /// Returns the total number of transactions in the given block.
621    ///
622    /// # Arguments
623    ///
624    /// * `block_hash` - The block hash.
625    ///
626    /// # Returns
627    ///
628    /// The number of transactions in the given block.
629    pub async fn transaction_count(&self, block_hash: B256) -> TransportResult<u64> {
630        self.instrument_function("eth_getBlockTransactionCountByHash", (block_hash,))
631            .await
632            .inspect_err(|err| error!("Failed to get transaction count: {:?}", err.to_string()))
633            .map(|result: U64| result.to())
634    }
635
636    /// Returns a single transaction at index in the given block.
637    ///
638    /// # Arguments
639    ///
640    /// * `block_hash` - The block hash.
641    /// * `index` - The index of the transaction in the block.
642    ///
643    /// # Returns
644    ///
645    /// The transaction at index in the given block.
646    pub async fn transaction_in_block(
647        &self,
648        block_hash: B256,
649        index: u64,
650    ) -> TransportResult<Transaction> {
651        self.instrument_function("eth_getTransactionByBlockHashAndIndex", (block_hash, index))
652            .await
653            .inspect_err(|err| error!("Failed to get transaction: {:?}", err.to_string()))
654    }
655
656    /// Returns the receipt of a transaction by transaction hash.
657    /// *Note:* the receipt is not available for pending transactions.
658    ///
659    /// # Arguments
660    ///
661    /// * `tx_hash` - The hash of the transaction.
662    ///
663    /// # Returns
664    ///
665    /// The transaction receipt.
666    pub async fn transaction_receipt(&self, tx_hash: B256) -> TransportResult<TransactionReceipt> {
667        self.instrument_function("eth_getTransactionReceipt", (tx_hash,))
668            .await
669            .inspect_err(|err| error!("Failed to get receipt: {:?}", err.to_string()))
670    }
671
672    /// Instrument a function call with the given method name and parameters.
673    ///
674    /// This function will measure the duration of the call and report it to the metrics collector.
675    ///
676    /// # Arguments
677    ///
678    /// * `rpc_method_name` - The name of the RPC method being called.
679    /// * `params` - The parameters to pass to the RPC method.
680    ///
681    /// # Returns
682    ///
683    /// The result of the RPC call.
684    async fn instrument_function<'async_trait, P, R>(
685        &self,
686        rpc_method_name: &str,
687        params: P,
688    ) -> TransportResult<R>
689    where
690        P: RpcSend + 'async_trait,
691        R: RpcRecv + 'async_trait,
692    {
693        let start = Instant::now();
694        let method_string = String::from(rpc_method_name);
695
696        // send the request with the provided client (http or ws)
697        let result = match (self.http_client.as_ref(), self.ws_client.as_ref()) {
698            (Some(http_client), _) => http_client.raw_request(method_string.into(), params).await,
699            (_, Some(ws_client)) => ws_client.raw_request(method_string.into(), params).await,
700            (_, _) => unreachable!(),
701        };
702
703        let rpc_request_duration = start.elapsed();
704
705        // we only observe the duration of successful calls (even though this is not well defined in the spec)
706        self.rpc_collector.set_rpc_request_duration_seconds(
707            rpc_method_name,
708            self.net_version.to_string().as_str(),
709            rpc_request_duration.as_secs_f64(),
710        );
711        result
712    }
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718    use alloy::consensus::{SignableTransaction, TxLegacy};
719    use alloy::network::TxSignerSync;
720    use alloy::primitives::address;
721    use alloy::primitives::{bytes, TxKind::Call, U256};
722    use alloy::rpc::types::eth::{pubsub::SubscriptionResult, BlockId, BlockNumberOrTag};
723    use alloy::signers::local::PrivateKeySigner;
724    use eigen_common::get_provider;
725    use eigen_testing_utils::anvil::{set_account_balance, start_anvil_container};
726    use eigen_testing_utils::transaction::wait_transaction;
727    use tokio;
728
729    #[tokio::test]
730    async fn test_suggest_gas_tip_cap() {
731        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
732
733        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
734        let fee_per_gas = instrumented_client.suggest_gas_tip_cap().await.unwrap();
735        let expected_fee_per_gas = get_provider(&http_endpoint)
736            .get_max_priority_fee_per_gas()
737            .await
738            .unwrap();
739        assert_eq!(expected_fee_per_gas as u64, fee_per_gas);
740    }
741
742    #[tokio::test]
743    async fn test_gas_price() {
744        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
745        let provider = get_provider(&http_endpoint);
746
747        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
748        let gas_price = instrumented_client.suggest_gas_price().await.unwrap();
749        let expected_gas_price = provider.clone().get_gas_price().await.unwrap();
750        assert_eq!(gas_price, expected_gas_price as u64);
751    }
752
753    #[tokio::test]
754    async fn test_sync_status() {
755        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
756        let provider = get_provider(&http_endpoint);
757
758        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
759        let sync_status = instrumented_client.sync_progress().await.unwrap();
760        let expected_sync_status = provider.clone().syncing().await.unwrap();
761        assert_eq!(expected_sync_status, sync_status);
762    }
763
764    #[tokio::test]
765    async fn test_chain_id() {
766        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
767        let provider = get_provider(&http_endpoint);
768
769        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
770
771        let expected_chain_id = provider.clone().get_chain_id().await.unwrap();
772        let chain_id = instrumented_client.chain_id().await.unwrap();
773
774        assert_eq!(expected_chain_id, chain_id);
775    }
776
777    #[tokio::test]
778    async fn test_balance_at() {
779        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
780        let provider = get_provider(&http_endpoint);
781
782        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
783        let address = provider.get_accounts().await.unwrap()[0];
784
785        let expected_balance_at = provider.get_balance(address).await.unwrap();
786        let balance_at = instrumented_client
787            .balance_at(address, BlockNumberOrTag::Latest)
788            .await
789            .unwrap();
790
791        assert_eq!(expected_balance_at, balance_at);
792    }
793
794    #[tokio::test]
795    async fn test_subscribe_new_head() {
796        let (_container, _http_endpoint, ws_endpoint) = start_anvil_container().await;
797
798        let instrumented_client = InstrumentedClient::new_ws(&ws_endpoint).await.unwrap();
799        let subscription: TransportResult<Subscription<SubscriptionResult>> =
800            instrumented_client.subscribe_new_head().await;
801        assert!(subscription.is_ok())
802    }
803
804    #[tokio::test]
805    async fn test_subscribe_filter_logs() {
806        let (_container, http_endpoint, ws_endpoint) = start_anvil_container().await;
807        let provider = get_provider(&http_endpoint);
808
809        let instrumented_client = InstrumentedClient::new_ws(&ws_endpoint).await.unwrap();
810        let address = provider.clone().get_accounts().await.unwrap()[0];
811        let filter = Filter::new().address(address.to_string().parse::<Address>().unwrap());
812
813        let subscription: TransportResult<Subscription<SubscriptionResult>> =
814            instrumented_client.subscribe_filter_logs(filter).await;
815
816        assert!(subscription.is_ok());
817    }
818
819    #[tokio::test]
820    async fn test_block_by_hash() {
821        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
822        let provider = get_provider(&http_endpoint);
823
824        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
825
826        // get the hash from the last block
827        let hash = provider
828            .get_block(BlockId::latest())
829            .await
830            .unwrap()
831            .unwrap()
832            .header
833            .hash;
834
835        let expected_block = provider.get_block_by_hash(hash).full().await.unwrap();
836        let block = instrumented_client.block_by_hash(hash).await.unwrap();
837
838        assert_eq!(expected_block, block);
839    }
840
841    #[tokio::test]
842    async fn test_block_by_number() {
843        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
844        let provider = get_provider(&http_endpoint);
845
846        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
847        let block_number = 1;
848
849        let expected_block = provider
850            .get_block_by_number(block_number.into())
851            .full()
852            .await
853            .unwrap();
854        let block = instrumented_client
855            .block_by_number(block_number.into())
856            .await
857            .unwrap();
858
859        assert_eq!(expected_block, block);
860    }
861
862    #[tokio::test]
863    async fn test_transaction_count() {
864        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
865        let provider = get_provider(&http_endpoint);
866
867        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
868
869        let block = provider
870            .get_block(BlockId::latest())
871            .await
872            .unwrap()
873            .unwrap();
874
875        let block_hash = block.header.hash;
876        let tx_count = instrumented_client
877            .transaction_count(B256::from_slice(block_hash.as_slice()))
878            .await
879            .unwrap();
880        let expected_tx_count = block.transactions.len();
881
882        assert_eq!(tx_count, expected_tx_count as u64);
883    }
884
885    /// This test tests the following methods
886    /// * `send_transaction`
887    /// * `transaction_by_hash`
888    /// * `transaction_receipt`
889    /// * `transaction_in_block`
890    #[tokio::test]
891    async fn test_transaction_methods() {
892        let (container, rpc_url, _ws_endpoint) = start_anvil_container().await;
893        let instrumented_client = InstrumentedClient::new(&rpc_url).await.unwrap();
894
895        // Set balance on a random address to use as sender
896        let private_key_hex =
897            "6b35c6d8110c888de06575b45181bf3f9e6c73451fa5cde812c95a6b31e66ddf".to_string();
898        let address = "009440d62dc85c73dbf889b7ad1f4da8b231d2ef";
899        set_account_balance(&container, address).await;
900
901        // build the transaction
902        let to = address!("a0Ee7A142d267C1f36714E4a8F75612F20a79720");
903        let mut tx = TxLegacy {
904            to: Call(to),
905            value: U256::from(0),
906            gas_limit: 2_000_000,
907            nonce: 0,
908            gas_price: 21_000_000_000,
909            input: bytes!(),
910            chain_id: Some(31337),
911        };
912
913        let signer = private_key_hex.parse::<PrivateKeySigner>().unwrap();
914        let signature = signer.sign_transaction_sync(&mut tx).unwrap();
915        let signed_tx = tx.into_signed(signature);
916        let tx: TxEnvelope = TxEnvelope::from(signed_tx);
917
918        // test send_transaction
919        let tx_hash = instrumented_client.send_transaction(tx).await;
920        assert!(tx_hash.is_ok());
921        let tx_hash = B256::from_slice(tx_hash.unwrap().as_ref());
922
923        // test transaction_by_hash
924        let tx_by_hash = instrumented_client.transaction_by_hash(tx_hash).await;
925        assert!(tx_by_hash.is_ok());
926
927        wait_transaction(&rpc_url, tx_hash).await.unwrap();
928
929        // test transaction_receipt
930        let receipt = instrumented_client.transaction_receipt(tx_hash).await;
931        assert!(receipt.is_ok());
932        let receipt = receipt.unwrap();
933
934        // test transaction_in_block
935        let tx_by_block = instrumented_client
936            .transaction_in_block(
937                receipt.block_hash.unwrap(),
938                receipt.transaction_index.unwrap(),
939            )
940            .await;
941        assert!(tx_by_block.is_ok());
942    }
943
944    #[tokio::test]
945    async fn test_estimate_gas() {
946        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
947        let provider = get_provider(&http_endpoint);
948
949        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
950        let accounts = provider.get_accounts().await.unwrap();
951        let from = accounts.first().unwrap();
952        let to = accounts.get(1).unwrap();
953
954        // build the transaction
955        let tx = TxLegacy {
956            to: Call(*to),
957            value: U256::from(0),
958            gas_limit: 2_000_000,
959            nonce: 0,
960            gas_price: 1_000_000,
961            input: bytes!(),
962            chain_id: Some(31337),
963        };
964        let tx_request: TransactionRequest = tx.clone().into();
965        let tx_request = tx_request.from(*from);
966
967        let expected_estimated_gas = provider
968            .clone()
969            .estimate_gas(tx_request.clone())
970            .await
971            .unwrap();
972        let estimated_gas = instrumented_client.estimate_gas(tx_request).await.unwrap();
973        assert_eq!(expected_estimated_gas, estimated_gas);
974    }
975
976    #[tokio::test]
977    async fn test_call_contract_and_pending_call_contract() {
978        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
979        let provider = get_provider(&http_endpoint);
980
981        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
982
983        let anvil = provider.clone();
984        let accounts = anvil.get_accounts().await.unwrap();
985        let from = accounts.first().unwrap();
986        let to = accounts.get(1).unwrap();
987
988        let nonce = instrumented_client
989            .nonce_at(*from, BlockNumberOrTag::Latest)
990            .await
991            .unwrap();
992
993        // build the transaction
994        let tx = TxLegacy {
995            to: Call(*to),
996            value: U256::from(0),
997            gas_limit: 1_000_000,
998            nonce,
999            gas_price: 21_000_000_000,
1000            input: bytes!(),
1001            chain_id: Some(31337),
1002        };
1003        let tx_request: TransactionRequest = tx.clone().into();
1004        let tx_request = tx_request.from(*from);
1005
1006        // test call_contract
1007        let expected_bytes = anvil.call(tx_request.clone()).await.unwrap();
1008        let bytes = instrumented_client
1009            .call_contract(tx_request.clone(), BlockNumberOrTag::Latest)
1010            .await
1011            .unwrap();
1012        assert_eq!(expected_bytes, bytes);
1013
1014        // test pending_call_contract
1015        let bytes = instrumented_client.pending_call_contract(tx_request).await;
1016        assert!(bytes.is_ok());
1017    }
1018
1019    #[tokio::test]
1020    async fn test_filter_logs() {
1021        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1022        let provider = get_provider(&http_endpoint);
1023
1024        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1025        let address = provider.clone().get_accounts().await.unwrap()[0];
1026        let filter = Filter::new().address(address.to_string().parse::<Address>().unwrap());
1027
1028        let expected_logs = provider.clone().get_logs(&filter).await.unwrap();
1029        let logs = instrumented_client.filter_logs(filter).await.unwrap();
1030
1031        assert_eq!(expected_logs, logs);
1032    }
1033
1034    #[tokio::test]
1035    async fn test_storage_at() {
1036        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1037        let provider = get_provider(&http_endpoint);
1038
1039        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1040
1041        let account = provider.clone().get_accounts().await.unwrap()[0];
1042        let expected_storage = provider
1043            .clone()
1044            .get_storage_at(account, U256::ZERO)
1045            .await
1046            .unwrap();
1047
1048        let block_number = instrumented_client.block_number().await.unwrap();
1049        let storage = instrumented_client
1050            .storage_at(account, U256::ZERO, U256::from(block_number))
1051            .await
1052            .unwrap();
1053
1054        assert_eq!(expected_storage, storage);
1055    }
1056
1057    #[tokio::test]
1058    async fn test_block_number() {
1059        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1060        let provider = get_provider(&http_endpoint);
1061
1062        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1063
1064        let expected_block_number = provider.clone().get_block_number().await.unwrap();
1065        let block_number = instrumented_client.block_number().await.unwrap();
1066
1067        assert_eq!(expected_block_number, block_number);
1068    }
1069
1070    #[tokio::test]
1071    async fn test_code_at() {
1072        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1073        let provider = get_provider(&http_endpoint);
1074
1075        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1076        let address = provider.get_accounts().await.unwrap()[0];
1077
1078        let expected_code = provider.get_code_at(address).await.unwrap();
1079        let code = instrumented_client
1080            .code_at(address, BlockNumberOrTag::Latest)
1081            .await
1082            .unwrap();
1083
1084        assert_eq!(expected_code, code);
1085    }
1086
1087    #[tokio::test]
1088    async fn test_fee_history() {
1089        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1090        let provider = get_provider(&http_endpoint);
1091
1092        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1093        let block_count = 4;
1094        let last_block = BlockNumberOrTag::Latest;
1095        let reward_percentiles = [0.2, 0.3];
1096
1097        let expected_fee_history = provider
1098            .get_fee_history(block_count, last_block, &reward_percentiles)
1099            .await
1100            .unwrap();
1101        let fee_history = instrumented_client
1102            .fee_history(block_count, last_block, &reward_percentiles)
1103            .await
1104            .unwrap();
1105
1106        assert_eq!(expected_fee_history, fee_history);
1107    }
1108
1109    #[tokio::test]
1110    async fn test_header_by_hash() {
1111        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1112        let provider = get_provider(&http_endpoint);
1113
1114        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1115        let hash = provider
1116            .get_block(BlockId::latest())
1117            .await
1118            .unwrap()
1119            .unwrap()
1120            .header
1121            .hash;
1122        let expected_header = provider
1123            .get_block_by_hash(hash)
1124            .await
1125            .unwrap()
1126            .unwrap()
1127            .header;
1128        let header = instrumented_client.header_by_hash(hash).await.unwrap();
1129
1130        assert_eq!(expected_header, header);
1131    }
1132
1133    #[tokio::test]
1134    async fn test_header_by_number() {
1135        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1136        let provider = get_provider(&http_endpoint);
1137
1138        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1139        let block_number = BlockNumberOrTag::Earliest;
1140
1141        let header = instrumented_client
1142            .header_by_number(block_number)
1143            .await
1144            .unwrap();
1145
1146        let expected_header = provider
1147            .get_block_by_number(block_number)
1148            .await
1149            .unwrap()
1150            .unwrap()
1151            .header;
1152
1153        assert_eq!(expected_header, header);
1154    }
1155
1156    #[tokio::test]
1157    async fn test_nonce_at() {
1158        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1159        let provider = get_provider(&http_endpoint);
1160
1161        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1162        let address = provider.get_accounts().await.unwrap()[0];
1163
1164        let expected_nonce = provider.get_transaction_count(address).await.unwrap();
1165        let nonce = instrumented_client
1166            .nonce_at(address, BlockNumberOrTag::Latest)
1167            .await
1168            .unwrap();
1169
1170        assert_eq!(expected_nonce, nonce);
1171    }
1172
1173    #[tokio::test]
1174    async fn test_pending_balance_at() {
1175        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1176        let provider = get_provider(&http_endpoint);
1177
1178        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1179        let address = provider.get_accounts().await.unwrap()[0];
1180
1181        // TODO: currently comparing "pending" balance with "latest" balance. Check for pending transactions?
1182        let expected_balance = provider.get_balance(address).await.unwrap();
1183        let balance = instrumented_client
1184            .pending_balance_at(address)
1185            .await
1186            .unwrap();
1187
1188        assert_eq!(expected_balance, balance);
1189    }
1190
1191    #[tokio::test]
1192    async fn test_pending_code_at() {
1193        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1194        let provider = get_provider(&http_endpoint);
1195
1196        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1197        let address = provider.get_accounts().await.unwrap()[0];
1198
1199        // TODO: currently comparing "pending" with "latest". Check for pending transactions?
1200        let expected_code = provider.get_code_at(address).await.unwrap();
1201        let code = instrumented_client.pending_code_at(address).await.unwrap();
1202
1203        assert_eq!(expected_code, code);
1204    }
1205
1206    #[tokio::test]
1207    async fn test_pending_nonce_at() {
1208        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1209        let provider = get_provider(&http_endpoint);
1210
1211        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1212        let address = provider.get_accounts().await.unwrap()[0];
1213
1214        // TODO: currently comparing "pending" with "latest". Check for pending transactions?
1215        let expected_pending_nonce_at = provider.get_transaction_count(address).await.unwrap();
1216        let pending_nonce_at = instrumented_client.pending_nonce_at(address).await.unwrap();
1217
1218        assert_eq!(expected_pending_nonce_at, pending_nonce_at);
1219    }
1220
1221    #[tokio::test]
1222    async fn test_pending_storage_at() {
1223        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1224        let provider = get_provider(&http_endpoint);
1225
1226        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1227        let address = provider.get_accounts().await.unwrap()[0];
1228        let key = U256::from(10);
1229
1230        // TODO: currently comparing "pending" with "latest". Check for pending transactions?
1231        // TODO: set storage and check change
1232        let expected_pending_storage_at = provider.get_storage_at(address, key).await.unwrap();
1233        let pending_storage_at = instrumented_client
1234            .pending_storage_at(address, key)
1235            .await
1236            .unwrap();
1237
1238        assert_eq!(expected_pending_storage_at, pending_storage_at);
1239    }
1240
1241    #[tokio::test]
1242    async fn test_pending_transaction_count() {
1243        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
1244        let provider = get_provider(&http_endpoint);
1245
1246        let instrumented_client = InstrumentedClient::new(&http_endpoint).await.unwrap();
1247
1248        let expected_transaction_count: u64 = provider
1249            .get_block_by_number(BlockNumberOrTag::Pending)
1250            .await
1251            .unwrap()
1252            .unwrap()
1253            .transactions
1254            .len() as u64;
1255
1256        let transaction_count = instrumented_client.pending_transaction_count().await;
1257
1258        assert_eq!(expected_transaction_count, transaction_count.unwrap());
1259    }
1260}