1use crate::client::BackendClient;
4use alloy_consensus::TxEnvelope;
5use alloy_json_rpc::{RpcRecv, RpcSend};
6use alloy_primitives::{Address, B256, BlockHash, BlockNumber, Bytes, ChainId, U64, U256};
7use alloy_provider::{Provider, ProviderBuilder, RootProvider, WsConnect};
8use alloy_pubsub::Subscription;
9use alloy_rlp::Encodable;
10use alloy_rpc_types_eth::{
11 Block, BlockNumberOrTag, FeeHistory, Filter, Header, Log, SyncStatus, Transaction,
12 TransactionReceipt, TransactionRequest,
13};
14use alloy_transport::{TransportError, TransportResult};
15use blueprint_metrics_rpc_calls::RpcCallsMetrics as RpcCallsCollector;
16use blueprint_std::string::String;
17use blueprint_std::string::ToString;
18use blueprint_std::time::Instant;
19use blueprint_std::vec::Vec;
20use hex;
21use thiserror::Error;
22use url::Url;
23
24const PENDING_TAG: &str = "pending";
25
26enum ProviderInner {
27 Http(RootProvider),
28 Ws(RootProvider),
29}
30
31pub struct InstrumentedClient {
34 inner: ProviderInner,
35 rpc_collector: RpcCallsCollector,
36 net_version: u64,
37}
38
39#[derive(Error, Debug)]
41pub enum InstrumentedClientError {
42 #[error("invalid url")]
43 InvalidUrl,
44 #[error("error getting version")]
45 ErrorGettingVersion,
46 #[error("error running command")]
47 CommandError,
48
49 #[error(transparent)]
50 Rpc(#[from] alloy_json_rpc::RpcError<alloy_transport::TransportErrorKind>),
51}
52
53impl Provider for InstrumentedClient {
54 fn root(&self) -> &RootProvider {
55 match &self.inner {
56 ProviderInner::Http(provider) | ProviderInner::Ws(provider) => provider,
57 }
58 }
59}
60
61impl BackendClient for InstrumentedClient {
62 type Error = InstrumentedClientError;
63
64 async fn block_number(&self) -> Result<BlockNumber, Self::Error> {
74 self.instrument_function("eth_blockNumber", ())
75 .await
76 .inspect_err(|err| {
77 blueprint_core::error!("Failed to get block number {:?}", err.to_string().as_str());
78 })
79 .map_err(|_err| InstrumentedClientError::CommandError)
80 .map(|result: U64| result.to())
81 }
82
83 async fn block_by_number(
97 &self,
98 number: BlockNumberOrTag,
99 ) -> Result<Option<Block>, Self::Error> {
100 self.instrument_function("eth_getBlockByNumber", (number, true))
101 .await
102 .inspect_err(|err| {
103 blueprint_core::error!(
104 "Failed to get block by number {:?}",
105 err.to_string().as_str()
106 );
107 })
108 .map_err(|_err| InstrumentedClientError::CommandError)
109 }
110}
111
112#[allow(clippy::missing_errors_doc)] impl InstrumentedClient {
114 pub async fn new<T: TryInto<Url>>(url: T) -> Result<Self, InstrumentedClientError> {
128 let url = url
129 .try_into()
130 .map_err(|_| InstrumentedClientError::InvalidUrl)?;
131 let http_client = ProviderBuilder::new()
132 .disable_recommended_fillers()
133 .connect_http(url);
134 let net_version = http_client
135 .get_net_version()
136 .await
137 .map_err(|_| InstrumentedClientError::ErrorGettingVersion)?;
138
139 let rpc_collector = RpcCallsCollector::new();
140 Ok(InstrumentedClient {
141 inner: ProviderInner::Http(http_client),
142 rpc_collector,
143 net_version,
144 })
145 }
146
147 pub async fn new_ws<T: TryInto<Url>>(url: T) -> Result<Self, InstrumentedClientError> {
161 let url = url
162 .try_into()
163 .map_err(|_| InstrumentedClientError::InvalidUrl)?;
164 let ws_connect = WsConnect::new(url);
165
166 let ws_client = ProviderBuilder::new()
167 .disable_recommended_fillers()
168 .connect_ws(ws_connect)
169 .await?;
170 let net_version = ws_client
171 .get_net_version()
172 .await
173 .map_err(|_| InstrumentedClientError::ErrorGettingVersion)?;
174
175 let rpc_collector = RpcCallsCollector::new();
176 Ok(InstrumentedClient {
177 inner: ProviderInner::Ws(ws_client),
178 rpc_collector,
179 net_version,
180 })
181 }
182
183 pub async fn new_from_client(client: RootProvider) -> Result<Self, InstrumentedClientError> {
197 let net_version = client
198 .get_net_version()
199 .await
200 .map_err(|_| InstrumentedClientError::ErrorGettingVersion)?;
201
202 let rpc_collector = RpcCallsCollector::new();
203 Ok(InstrumentedClient {
204 inner: ProviderInner::Http(client),
205 rpc_collector,
206 net_version,
207 })
208 }
209
210 pub async fn chain_id(&self) -> TransportResult<ChainId> {
220 self.instrument_function("eth_chainId", ())
221 .await
222 .inspect_err(|err| {
223 blueprint_core::error!("Failed to get chain id {:?}", err.to_string().as_str());
224 })
225 .map(|result: U64| result.to())
226 }
227
228 pub async fn balance_at(
239 &self,
240 account: Address,
241 block_number: BlockNumberOrTag,
242 ) -> TransportResult<U256> {
243 self.instrument_function("eth_getBalance", (account, block_number))
244 .await
245 .inspect_err(|err| {
246 blueprint_core::error!("Failed to get balance {:?}", err.to_string().as_str());
247 })
248 }
249
250 pub async fn block_by_hash(&self, hash: BlockHash) -> TransportResult<Option<Block>> {
260 self.instrument_function("eth_getBlockByHash", (hash, true))
261 .await
262 .inspect_err(|err| {
263 blueprint_core::error!(
264 "Failed to get block by hash {:?}",
265 err.to_string().as_str()
266 );
267 })
268 }
269
270 pub async fn call_contract(
281 &self,
282 call: TransactionRequest,
283 block_number: BlockNumberOrTag,
284 ) -> TransportResult<Bytes> {
285 self.instrument_function("eth_call", (call, block_number))
286 .await
287 .inspect_err(|err| {
288 blueprint_core::error!("Failed to call contract {:?}", err.to_string().as_str());
289 })
290 }
291
292 pub async fn code_at(
303 &self,
304 address: Address,
305 block_number: BlockNumberOrTag,
306 ) -> TransportResult<Bytes> {
307 self.instrument_function("eth_getCode", (address, block_number))
308 .await
309 .inspect_err(|err| {
310 blueprint_core::error!("Failed to get code {:?}", err.to_string().as_str());
311 })
312 }
313
314 pub async fn estimate_gas(&self, tx: TransactionRequest) -> TransportResult<u64> {
324 self.instrument_function("eth_estimateGas", (tx,))
325 .await
326 .inspect_err(|err| {
327 blueprint_core::error!("Failed to estimate gas {:?}", err.to_string().as_str());
328 })
329 .map(|result: U64| result.to())
330 }
331
332 pub async fn fee_history(
342 &self,
343 block_count: u64,
344 last_block: BlockNumberOrTag,
345 reward_percentiles: &[f64],
346 ) -> TransportResult<FeeHistory> {
347 self.instrument_function(
348 "eth_feeHistory",
349 (block_count, last_block, reward_percentiles),
350 )
351 .await
352 .inspect_err(|err| {
353 blueprint_core::error!("Failed to get fee history {:?}", err.to_string().as_str());
354 })
355 }
356
357 pub async fn filter_logs(&self, filter: Filter) -> TransportResult<Vec<Log>> {
367 self.instrument_function("eth_getLogs", (filter,))
368 .await
369 .inspect_err(|err| {
370 blueprint_core::error!("Failed to get filter logs {:?}", err.to_string().as_str());
371 })
372 }
373
374 pub async fn header_by_hash(&self, hash: B256) -> TransportResult<Header> {
384 let transaction_detail = false;
385 self.instrument_function("eth_getBlockByHash", (hash, transaction_detail))
386 .await
387 .inspect_err(|err| {
388 blueprint_core::error!(
389 "Failed to get header by hash {:?}",
390 err.to_string().as_str()
391 );
392 })
393 }
394
395 pub async fn header_by_number(
405 &self,
406 block_number: BlockNumberOrTag,
407 ) -> TransportResult<Header> {
408 let transaction_detail = false;
409 self.instrument_function("eth_getBlockByNumber", (block_number, transaction_detail))
410 .await
411 .inspect_err(|err| {
412 blueprint_core::error!(
413 "Failed to get header by number {:?}",
414 err.to_string().as_str()
415 );
416 })
417 }
418
419 pub async fn nonce_at(
430 &self,
431 account: Address,
432 block_number: BlockNumberOrTag,
433 ) -> TransportResult<u64> {
434 self.instrument_function("eth_getTransactionCount", (account, block_number))
435 .await
436 .inspect_err(|err| {
437 blueprint_core::error!("Failed to get nonce {:?}", err.to_string().as_str());
438 })
439 .map(|result: U64| result.to())
440 }
441
442 pub async fn pending_balance_at(&self, account: Address) -> TransportResult<U256> {
452 self.instrument_function("eth_getBalance", (account, PENDING_TAG))
453 .await
454 .inspect_err(|err| {
455 blueprint_core::error!(
456 "Failed to get pending balance {:?}",
457 err.to_string().as_str()
458 );
459 })
460 }
461
462 pub async fn pending_call_contract(&self, call: TransactionRequest) -> TransportResult<Bytes> {
474 self.call_contract(call, BlockNumberOrTag::Pending).await
475 }
476
477 pub async fn pending_code_at(&self, account: Address) -> TransportResult<Bytes> {
487 self.instrument_function("eth_getCode", (account, PENDING_TAG))
488 .await
489 .inspect_err(|err| {
490 blueprint_core::error!("Failed to get pending code {:?}", err.to_string().as_str());
491 })
492 }
493
494 pub async fn pending_nonce_at(&self, account: Address) -> TransportResult<u64> {
506 self.instrument_function("eth_getTransactionCount", (account, PENDING_TAG))
507 .await
508 .inspect_err(|err| {
509 blueprint_core::error!(
510 "Failed to get pending nonce {:?}",
511 err.to_string().as_str()
512 );
513 })
514 .map(|result: U64| result.to())
515 }
516
517 pub async fn pending_storage_at(&self, account: Address, key: U256) -> TransportResult<U256> {
528 self.instrument_function("eth_getStorageAt", (account, key, PENDING_TAG))
529 .await
530 .inspect_err(|err| {
531 blueprint_core::error!(
532 "Failed to get pending storage {:?}",
533 err.to_string().as_str()
534 );
535 })
536 }
537
538 pub async fn pending_transaction_count(&self) -> TransportResult<u64> {
544 self.instrument_function("eth_getBlockTransactionCountByNumber", (PENDING_TAG,))
545 .await
546 .inspect_err(|err| {
547 blueprint_core::error!(
548 "Failed to get transaction count {:?}",
549 err.to_string().as_str()
550 );
551 })
552 .map(|result: U64| result.to())
553 }
554
555 pub async fn send_transaction(&self, tx: TxEnvelope) -> TransportResult<B256> {
565 let mut encoded_tx = Vec::new();
566 tx.encode(&mut encoded_tx);
567 self.instrument_function("eth_sendRawTransaction", (hex::encode(encoded_tx),))
568 .await
569 .inspect_err(|err| {
570 blueprint_core::error!("Failed to send transaction {:?}", err.to_string().as_str());
571 })
572 }
573
574 pub async fn storage_at(
586 &self,
587 account: Address,
588 key: U256,
589 block_number: U256,
590 ) -> TransportResult<U256> {
591 self.instrument_function("eth_getStorageAt", (account, key, block_number))
592 .await
593 .inspect_err(|err| {
594 blueprint_core::error!("Failed to get storage {:?}", err.to_string().as_str());
595 })
596 }
597
598 pub async fn subscribe_filter_logs<R: RpcRecv>(
612 &self,
613 filter: Filter,
614 ) -> TransportResult<Subscription<R>> {
615 let id: U256 = self
616 .instrument_function("eth_subscribe", ("logs", filter))
617 .await
618 .inspect_err(|err| {
619 blueprint_core::error!(
620 "Failed to get logs subscription id {:?}",
621 err.to_string().as_str(),
622 );
623 })?;
624
625 let ProviderInner::Ws(ws_client) = &self.inner else {
626 return Err(TransportError::UnsupportedFeature(
627 "http client does not support eth_subscribe calls.",
628 ));
629 };
630
631 ws_client.get_subscription(id.into()).await
632 }
633
634 pub async fn subscribe_new_head<R: RpcRecv>(&self) -> TransportResult<Subscription<R>> {
644 let id: U256 = self
645 .instrument_function("eth_subscribe", ("newHeads",))
646 .await
647 .inspect_err(|err| {
648 blueprint_core::error!(
649 "Failed to subscribe new head {:?}",
650 err.to_string().as_str()
651 );
652 })?;
653
654 let ProviderInner::Ws(ws_client) = &self.inner else {
655 return Err(TransportError::UnsupportedFeature(
656 "http client does not support eth_subscribe calls.",
657 ));
658 };
659
660 ws_client.get_subscription(id.into()).await
661 }
662
663 pub async fn suggest_gas_price(&self) -> TransportResult<u64> {
669 self.instrument_function("eth_gasPrice", ())
670 .await
671 .inspect_err(|err| {
672 blueprint_core::error!(
673 "Failed to suggest gas price {:?}",
674 err.to_string().as_str()
675 );
676 })
677 .map(|result: U64| result.to())
678 }
679
680 pub async fn suggest_gas_tip_cap(&self) -> TransportResult<u64> {
686 self.instrument_function("eth_maxPriorityFeePerGas", ())
687 .await
688 .inspect_err(|err| {
689 blueprint_core::error!(
690 "Failed to suggest gas tip cap {:?}",
691 err.to_string().as_str()
692 );
693 })
694 .map(|result: U64| result.to())
695 }
696
697 pub async fn sync_progress(&self) -> TransportResult<SyncStatus> {
705 self.instrument_function("eth_syncing", ())
706 .await
707 .inspect_err(|err| {
708 blueprint_core::error!(
709 "Failed to get sync progress {:?}",
710 err.to_string().as_str()
711 );
712 })
713 }
714
715 pub async fn transaction_by_hash(&self, tx_hash: B256) -> TransportResult<Transaction> {
725 self.instrument_function("eth_getTransactionByHash", (tx_hash,))
726 .await
727 .inspect_err(|err| {
728 blueprint_core::error!(
729 "Failed to get transaction by hash {:?}",
730 err.to_string().as_str(),
731 );
732 })
733 }
734
735 pub async fn transaction_count(&self, block_hash: B256) -> TransportResult<u64> {
745 self.instrument_function("eth_getBlockTransactionCountByHash", (block_hash,))
746 .await
747 .inspect_err(|err| {
748 blueprint_core::error!(
749 "Failed to get transaction count {:?}",
750 err.to_string().as_str()
751 );
752 })
753 .map(|result: U64| result.to())
754 }
755
756 pub async fn transaction_in_block(
767 &self,
768 block_hash: B256,
769 index: u64,
770 ) -> TransportResult<Transaction> {
771 self.instrument_function("eth_getTransactionByBlockHashAndIndex", (block_hash, index))
772 .await
773 .inspect_err(|err| {
774 blueprint_core::error!("Failed to get transaction {:?}", err.to_string().as_str());
775 })
776 }
777
778 pub async fn transaction_receipt(&self, tx_hash: B256) -> TransportResult<TransactionReceipt> {
790 self.instrument_function("eth_getTransactionReceipt", (tx_hash,))
791 .await
792 .inspect_err(|err| {
793 blueprint_core::error!("Failed to get receipt {:?}", err.to_string().as_str());
794 })
795 }
796
797 async fn instrument_function<P, R>(
810 &self,
811 rpc_method_name: &str,
812 params: P,
813 ) -> TransportResult<R>
814 where
815 P: RpcSend,
816 R: RpcRecv,
817 {
818 let start = Instant::now();
819 let method_string = String::from(rpc_method_name);
820
821 let result = self.raw_request(method_string.into(), params).await;
823 let rpc_request_duration = start.elapsed();
824
825 self.rpc_collector.set_rpc_request_duration_seconds(
827 rpc_method_name,
828 self.net_version.to_string().as_str(),
829 rpc_request_duration.as_secs_f64(),
830 );
831 result
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 use super::*;
838 use alloy_consensus::{SignableTransaction, TxLegacy};
839 use alloy_network::TxSignerSync;
840 use alloy_primitives::address;
841 use alloy_primitives::{TxKind::Call, U256, bytes};
842 use alloy_rpc_types::eth::{BlockId, BlockNumberOrTag, pubsub::SubscriptionResult};
843 use alloy_signer_local::PrivateKeySigner;
844 use blueprint_chain_setup_anvil::start_default_anvil_testnet;
845 use blueprint_evm_extra::util::get_provider_http;
846 use blueprint_evm_extra::util::wait_transaction;
847 use tokio;
848
849 #[tokio::test]
850 async fn test_suggest_gas_tip_cap() {
851 let testnet = start_default_anvil_testnet(false).await;
852
853 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
854 .await
855 .unwrap();
856 let fee_per_gas = instrumented_client.suggest_gas_tip_cap().await.unwrap();
857 let expected_fee_per_gas = get_provider_http(testnet.http_endpoint.clone())
858 .get_max_priority_fee_per_gas()
859 .await
860 .unwrap();
861 assert_eq!(expected_fee_per_gas, u128::from(fee_per_gas));
862 }
863
864 #[tokio::test]
865 async fn test_gas_price() {
866 let testnet = start_default_anvil_testnet(false).await;
867 let provider = get_provider_http(testnet.http_endpoint.clone());
868
869 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
870 .await
871 .unwrap();
872 let gas_price = instrumented_client.suggest_gas_price().await.unwrap();
873 let expected_gas_price = provider.clone().get_gas_price().await.unwrap();
874 assert_eq!(u128::from(gas_price), expected_gas_price);
875 }
876
877 #[tokio::test]
878 async fn test_sync_status() {
879 let testnet = start_default_anvil_testnet(false).await;
880 let provider = get_provider_http(testnet.http_endpoint.clone());
881
882 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
883 .await
884 .unwrap();
885 let sync_status = instrumented_client.sync_progress().await.unwrap();
886 let expected_sync_status = provider.clone().syncing().await.unwrap();
887 assert_eq!(expected_sync_status, sync_status);
888 }
889
890 #[tokio::test]
891 async fn test_chain_id() {
892 let testnet = start_default_anvil_testnet(false).await;
893 let provider = get_provider_http(testnet.http_endpoint.clone());
894
895 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
896 .await
897 .unwrap();
898
899 let expected_chain_id = provider.clone().get_chain_id().await.unwrap();
900 let chain_id = instrumented_client.chain_id().await.unwrap();
901
902 assert_eq!(expected_chain_id, chain_id);
903 }
904
905 #[tokio::test]
906 async fn test_balance_at() {
907 let testnet = start_default_anvil_testnet(false).await;
908 let provider = get_provider_http(testnet.http_endpoint.clone());
909
910 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
911 .await
912 .unwrap();
913 let address = provider.get_accounts().await.unwrap()[0];
914
915 let expected_balance_at = provider.get_balance(address).await.unwrap();
916 let balance_at = instrumented_client
917 .balance_at(address, BlockNumberOrTag::Latest)
918 .await
919 .unwrap();
920
921 assert_eq!(expected_balance_at, balance_at);
922 }
923
924 #[tokio::test]
925 async fn test_subscribe_new_head() {
926 let testnet = start_default_anvil_testnet(false).await;
927
928 let instrumented_client = InstrumentedClient::new_ws(testnet.ws_endpoint)
929 .await
930 .unwrap();
931 let subscription: TransportResult<Subscription<SubscriptionResult>> =
932 instrumented_client.subscribe_new_head().await;
933 assert!(subscription.is_ok());
934 }
935
936 #[tokio::test]
937 async fn test_subscribe_filter_logs() {
938 let testnet = start_default_anvil_testnet(false).await;
939 let provider = get_provider_http(testnet.http_endpoint.clone());
940
941 let instrumented_client = InstrumentedClient::new_ws(testnet.ws_endpoint)
942 .await
943 .unwrap();
944 let address = provider.clone().get_accounts().await.unwrap()[0];
945 let filter = Filter::new().address(address.to_string().parse::<Address>().unwrap());
946
947 let subscription: TransportResult<Subscription<SubscriptionResult>> =
948 instrumented_client.subscribe_filter_logs(filter).await;
949
950 assert!(subscription.is_ok());
951 }
952
953 #[tokio::test]
954 async fn test_block_by_hash() {
955 let testnet = start_default_anvil_testnet(false).await;
956 let provider = get_provider_http(testnet.http_endpoint.clone());
957
958 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
959 .await
960 .unwrap();
961
962 let hash = provider
964 .get_block(BlockId::latest())
965 .await
966 .unwrap()
967 .unwrap()
968 .header
969 .hash;
970
971 let expected_block = provider.get_block_by_hash(hash).full().await.unwrap();
972 let block = instrumented_client.block_by_hash(hash).await.unwrap();
973
974 assert_eq!(expected_block, block);
975 }
976
977 #[tokio::test]
978 async fn test_block_by_number() {
979 let testnet = start_default_anvil_testnet(false).await;
980 let provider = get_provider_http(testnet.http_endpoint.clone());
981
982 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
983 .await
984 .unwrap();
985 let block_number = 1;
986
987 let expected_block = provider
988 .get_block_by_number(block_number.into())
989 .full()
990 .await
991 .unwrap();
992 let block = instrumented_client
993 .block_by_number(block_number.into())
994 .await
995 .unwrap();
996
997 assert_eq!(expected_block, block);
998 }
999
1000 #[tokio::test]
1001 async fn test_transaction_count() {
1002 let testnet = start_default_anvil_testnet(false).await;
1003 let provider = get_provider_http(testnet.http_endpoint.clone());
1004
1005 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1006 .await
1007 .unwrap();
1008
1009 let block = provider
1010 .get_block(BlockId::latest())
1011 .await
1012 .unwrap()
1013 .unwrap();
1014
1015 let block_hash = block.header.hash;
1016 let tx_count = instrumented_client
1017 .transaction_count(B256::from_slice(block_hash.as_slice()))
1018 .await
1019 .unwrap();
1020 let expected_tx_count = block.transactions.len();
1021
1022 assert_eq!(tx_count, expected_tx_count as u64);
1023 }
1024
1025 #[tokio::test]
1031 #[ignore]
1032 async fn test_transaction_methods() {
1033 let testnet = start_default_anvil_testnet(false).await;
1034 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1035 .await
1036 .unwrap();
1037
1038 let to = address!("a0Ee7A142d267C1f36714E4a8F75612F20a79720");
1040 let mut tx = TxLegacy {
1041 to: Call(to),
1042 value: U256::from(0),
1043 gas_limit: 2_000_000,
1044 nonce: 0x69, gas_price: 21_000_000_000,
1046 input: bytes!(),
1047 chain_id: Some(31337),
1048 };
1049
1050 let private_key_hex =
1051 "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string();
1052 let signer: PrivateKeySigner = private_key_hex.parse().unwrap();
1053 let signature = signer.sign_transaction_sync(&mut tx).unwrap();
1054 let signed_tx = tx.into_signed(signature);
1055 let tx: TxEnvelope = TxEnvelope::from(signed_tx);
1056
1057 let tx_hash = instrumented_client.send_transaction(tx).await;
1059 assert!(tx_hash.is_ok());
1060 let tx_hash = B256::from_slice(tx_hash.unwrap().as_ref());
1061
1062 let tx_by_hash = instrumented_client.transaction_by_hash(tx_hash).await;
1064 assert!(tx_by_hash.is_ok());
1065
1066 wait_transaction(testnet.http_endpoint.clone(), tx_hash)
1067 .await
1068 .unwrap();
1069
1070 let receipt = instrumented_client.transaction_receipt(tx_hash).await;
1072 assert!(receipt.is_ok());
1073 let receipt = receipt.unwrap();
1074
1075 let tx_by_block = instrumented_client
1077 .transaction_in_block(
1078 receipt.block_hash.unwrap(),
1079 receipt.transaction_index.unwrap(),
1080 )
1081 .await;
1082 assert!(tx_by_block.is_ok());
1083 }
1084
1085 #[tokio::test]
1086 async fn test_estimate_gas() {
1087 let testnet = start_default_anvil_testnet(false).await;
1088 let provider = get_provider_http(testnet.http_endpoint.clone());
1089
1090 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1091 .await
1092 .unwrap();
1093 let accounts = provider.get_accounts().await.unwrap();
1094 let from = accounts.first().unwrap();
1095 let to = accounts.get(1).unwrap();
1096
1097 let tx = TxLegacy {
1099 to: Call(*to),
1100 value: U256::from(0),
1101 gas_limit: 2_000_000,
1102 nonce: 0,
1103 gas_price: 1_000_000,
1104 input: bytes!(),
1105 chain_id: Some(31337),
1106 };
1107 let tx_request: TransactionRequest = tx.clone().into();
1108 let tx_request = tx_request.from(*from);
1109
1110 let expected_estimated_gas = provider
1111 .clone()
1112 .estimate_gas(tx_request.clone())
1113 .await
1114 .unwrap();
1115 let estimated_gas = instrumented_client.estimate_gas(tx_request).await.unwrap();
1116 assert_eq!(expected_estimated_gas, estimated_gas);
1117 }
1118
1119 #[tokio::test]
1120 async fn test_call_contract_and_pending_call_contract() {
1121 let testnet = start_default_anvil_testnet(false).await;
1122 let provider = get_provider_http(testnet.http_endpoint.clone());
1123
1124 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1125 .await
1126 .unwrap();
1127
1128 let anvil = provider.clone();
1129 let accounts = anvil.get_accounts().await.unwrap();
1130 let from = accounts.first().unwrap();
1131 let to = accounts.get(1).unwrap();
1132
1133 let nonce = instrumented_client
1134 .nonce_at(*from, BlockNumberOrTag::Latest)
1135 .await
1136 .unwrap();
1137
1138 let tx = TxLegacy {
1140 to: Call(*to),
1141 value: U256::from(0),
1142 gas_limit: 1_000_000,
1143 nonce,
1144 gas_price: 21_000_000_000,
1145 input: bytes!(),
1146 chain_id: Some(31337),
1147 };
1148 let tx_request: TransactionRequest = tx.clone().into();
1149 let tx_request = tx_request.from(*from);
1150
1151 let expected_bytes = anvil.call(tx_request.clone()).await.unwrap();
1153 let bytes = instrumented_client
1154 .call_contract(tx_request.clone(), BlockNumberOrTag::Earliest)
1155 .await
1156 .unwrap();
1157 assert_eq!(expected_bytes, bytes);
1158
1159 let bytes = instrumented_client.pending_call_contract(tx_request).await;
1161 assert!(bytes.is_ok());
1162 }
1163
1164 #[tokio::test]
1165 async fn test_filter_logs() {
1166 let testnet = start_default_anvil_testnet(false).await;
1167 let provider = get_provider_http(testnet.http_endpoint.clone());
1168
1169 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1170 .await
1171 .unwrap();
1172 let address = provider.clone().get_accounts().await.unwrap()[0];
1173 let filter = Filter::new().address(address.to_string().parse::<Address>().unwrap());
1174
1175 let expected_logs = provider.clone().get_logs(&filter).await.unwrap();
1176 let logs = instrumented_client.filter_logs(filter).await.unwrap();
1177
1178 assert_eq!(expected_logs, logs);
1179 }
1180
1181 #[tokio::test]
1182 async fn test_storage_at() {
1183 let testnet = start_default_anvil_testnet(false).await;
1184 let provider = get_provider_http(testnet.http_endpoint.clone());
1185
1186 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1187 .await
1188 .unwrap();
1189
1190 let account = provider.clone().get_accounts().await.unwrap()[0];
1191 let expected_storage = provider
1192 .clone()
1193 .get_storage_at(account, U256::ZERO)
1194 .await
1195 .unwrap();
1196
1197 let storage = instrumented_client
1198 .storage_at(account, U256::ZERO, U256::ZERO)
1199 .await
1200 .unwrap();
1201
1202 assert_eq!(expected_storage, storage);
1203 }
1204
1205 #[tokio::test]
1206 async fn test_block_number() {
1207 let testnet = start_default_anvil_testnet(false).await;
1208 let provider = get_provider_http(testnet.http_endpoint.clone());
1209
1210 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1211 .await
1212 .unwrap();
1213
1214 let _output = testnet
1216 .container
1217 .exec(testcontainers::core::ExecCommand::new([
1218 "cast",
1219 "rpc",
1220 "evm_setAutomine",
1221 "false",
1222 ]))
1223 .await
1224 .expect("Failed to mine anvil blocks");
1225 tokio::time::sleep(blueprint_std::time::Duration::from_secs(5)).await;
1226
1227 let before = provider.clone().get_block_number().await.unwrap();
1228 let block_number = instrumented_client.block_number().await.unwrap();
1229 let after = provider.get_block_number().await.unwrap();
1230
1231 assert!(
1232 block_number >= before && block_number <= after,
1233 "instrumented block number {block_number} not within provider range {before}..={after}"
1234 );
1235 }
1236
1237 #[tokio::test]
1238 async fn test_code_at() {
1239 let testnet = start_default_anvil_testnet(false).await;
1240 let provider = get_provider_http(testnet.http_endpoint.clone());
1241
1242 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1243 .await
1244 .unwrap();
1245 let address = provider.get_accounts().await.unwrap()[0];
1246
1247 let expected_code = provider.get_code_at(address).await.unwrap();
1248 let code = instrumented_client
1249 .code_at(address, BlockNumberOrTag::Latest)
1250 .await
1251 .unwrap();
1252
1253 assert_eq!(expected_code, code);
1254 }
1255
1256 #[tokio::test]
1257 async fn test_fee_history() {
1258 let testnet = start_default_anvil_testnet(false).await;
1259 let provider = get_provider_http(testnet.http_endpoint.clone());
1260
1261 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1262 .await
1263 .unwrap();
1264
1265 let _output = testnet
1267 .container
1268 .exec(testcontainers::core::ExecCommand::new([
1269 "cast",
1270 "rpc",
1271 "evm_setAutomine",
1272 "false",
1273 ]))
1274 .await
1275 .expect("Failed to mine anvil blocks");
1276 tokio::time::sleep(blueprint_std::time::Duration::from_secs(5)).await;
1277
1278 let block_count = 4;
1279 let last_block = BlockNumberOrTag::Latest;
1280 let reward_percentiles = [0.2, 0.3];
1281
1282 let expected_fee_history = provider
1283 .get_fee_history(block_count, last_block, &reward_percentiles)
1284 .await
1285 .unwrap();
1286 let fee_history = instrumented_client
1287 .fee_history(block_count, last_block, &reward_percentiles)
1288 .await
1289 .unwrap();
1290
1291 assert_eq!(expected_fee_history, fee_history);
1292 }
1293
1294 #[tokio::test]
1295 async fn test_header_by_hash() {
1296 let testnet = start_default_anvil_testnet(false).await;
1297 let provider = get_provider_http(testnet.http_endpoint.clone());
1298
1299 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1300 .await
1301 .unwrap();
1302 let hash = provider
1303 .get_block(BlockId::latest())
1304 .await
1305 .unwrap()
1306 .unwrap()
1307 .header
1308 .hash;
1309 let expected_header = provider
1310 .get_block_by_hash(hash)
1311 .await
1312 .unwrap()
1313 .unwrap()
1314 .header;
1315 let header = instrumented_client.header_by_hash(hash).await.unwrap();
1316
1317 assert_eq!(expected_header, header);
1318 }
1319
1320 #[tokio::test]
1321 async fn test_header_by_number() {
1322 let testnet = start_default_anvil_testnet(false).await;
1323 let provider = get_provider_http(testnet.http_endpoint.clone());
1324
1325 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1326 .await
1327 .unwrap();
1328 let block_number = BlockNumberOrTag::Earliest;
1329
1330 let header = instrumented_client
1331 .header_by_number(block_number)
1332 .await
1333 .unwrap();
1334
1335 let expected_header = provider
1336 .get_block_by_number(block_number)
1337 .await
1338 .unwrap()
1339 .unwrap()
1340 .header;
1341
1342 assert_eq!(expected_header, header);
1343 }
1344
1345 #[tokio::test]
1346 async fn test_nonce_at() {
1347 let testnet = start_default_anvil_testnet(false).await;
1348 let provider = get_provider_http(testnet.http_endpoint.clone());
1349
1350 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1351 .await
1352 .unwrap();
1353 let address = provider.get_accounts().await.unwrap()[0];
1354
1355 let expected_nonce = provider.get_transaction_count(address).await.unwrap();
1356 let nonce = instrumented_client
1357 .nonce_at(address, BlockNumberOrTag::Latest)
1358 .await
1359 .unwrap();
1360
1361 assert_eq!(expected_nonce, nonce);
1362 }
1363
1364 #[tokio::test]
1365 async fn test_pending_balance_at() {
1366 let testnet = start_default_anvil_testnet(false).await;
1367 let provider = get_provider_http(testnet.http_endpoint.clone());
1368
1369 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1370 .await
1371 .unwrap();
1372 let address = provider.get_accounts().await.unwrap()[0];
1373
1374 let expected_balance = provider.get_balance(address).await.unwrap();
1376 let balance = instrumented_client
1377 .pending_balance_at(address)
1378 .await
1379 .unwrap();
1380
1381 assert_eq!(expected_balance, balance);
1382 }
1383
1384 #[tokio::test]
1385 async fn test_pending_code_at() {
1386 let testnet = start_default_anvil_testnet(false).await;
1387 let provider = get_provider_http(testnet.http_endpoint.clone());
1388
1389 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1390 .await
1391 .unwrap();
1392 let address = provider.get_accounts().await.unwrap()[0];
1393
1394 let expected_code = provider.get_code_at(address).await.unwrap();
1396 let code = instrumented_client.pending_code_at(address).await.unwrap();
1397
1398 assert_eq!(expected_code, code);
1399 }
1400
1401 #[tokio::test]
1402 async fn test_pending_nonce_at() {
1403 let testnet = start_default_anvil_testnet(false).await;
1404 let provider = get_provider_http(testnet.http_endpoint.clone());
1405
1406 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1407 .await
1408 .unwrap();
1409 let address = provider.get_accounts().await.unwrap()[0];
1410
1411 let expected_pending_nonce_at = provider.get_transaction_count(address).await.unwrap();
1413 let pending_nonce_at = instrumented_client.pending_nonce_at(address).await.unwrap();
1414
1415 assert_eq!(expected_pending_nonce_at, pending_nonce_at);
1416 }
1417
1418 #[tokio::test]
1419 async fn test_pending_storage_at() {
1420 let testnet = start_default_anvil_testnet(false).await;
1421 let provider = get_provider_http(testnet.http_endpoint.clone());
1422
1423 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1424 .await
1425 .unwrap();
1426 let address = provider.get_accounts().await.unwrap()[0];
1427 let key = U256::from(10);
1428
1429 let expected_pending_storage_at = provider.get_storage_at(address, key).await.unwrap();
1432 let pending_storage_at = instrumented_client
1433 .pending_storage_at(address, key)
1434 .await
1435 .unwrap();
1436
1437 assert_eq!(expected_pending_storage_at, pending_storage_at);
1438 }
1439
1440 #[tokio::test]
1441 async fn test_pending_transaction_count() {
1442 let testnet = start_default_anvil_testnet(false).await;
1443 let provider = get_provider_http(testnet.http_endpoint.clone());
1444
1445 let instrumented_client = InstrumentedClient::new(testnet.http_endpoint.clone())
1446 .await
1447 .unwrap();
1448
1449 let expected_transaction_count: u64 = provider
1450 .get_block_by_number(BlockNumberOrTag::Pending)
1451 .await
1452 .unwrap()
1453 .unwrap()
1454 .transactions
1455 .len() as u64;
1456
1457 let transaction_count = instrumented_client.pending_transaction_count().await;
1458
1459 assert_eq!(expected_transaction_count, transaction_count.unwrap());
1460 }
1461}