Repository: keep-starknet-strange/beerus (rust)

Issue:
feat: Ethereum RPC "get_transaction_count" call
# Description

Implement "get_transaction_count" Ethereum RPC call

Helios implmentation: https://github.com/a16z/helios/blob/ac8a145ae33e72b62e017749fd0d3e1887d3999e/client/src/rpc.rs#L59


# Acceptance Criteria

- Beerus can query a Ethereum's txs count
- Associated cli command
- Associated rest api wrapper
- Helios integration
- Units tests
- Integration tests


Pull request description:
feat: Ethereum "get_transaction_count"
<!--- Please provide a general summary of your changes in the title above -->

# Pull Request type

<!-- Please try to limit your pull request to one type; submit multiple pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no API changes)
- [ ] Build-related changes
- [ ] Documentation content changes
- [x] Testing
- [ ] Other (please describe):

# What is the current behavior?

<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: fix #140 

# What is the new behavior?

<!-- Please describe the behavior or changes that are being added by this PR. -->
- add eth get_transaction_count to core
- add eth query_transaction_count to CLI
- add eth tx_count to API

# Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this does introduce a breaking change, please describe the impact and migration path for existing applications below. -->

# Other information

<!-- Any other information that is important to this PR, such as screenshots of how the component looks before and after the change. -->


Patch:
diff --git a/beerus_cli/src/ethereum/mod.rs b/beerus_cli/src/ethereum/mod.rs
index f59db90..90bccf2 100644
--- a/beerus_cli/src/ethereum/mod.rs
+++ b/beerus_cli/src/ethereum/mod.rs
@@ -140,6 +140,32 @@ pub async fn query_code(beerus: BeerusLightClient, address: String) -> Result<Co
     Ok(CommandResponse::EthereumQueryCode(code))
 }
 
+/// Query tx count of an Ethereum address from a given block.
+/// # Arguments
+/// * `beerus` - The Beerus light client.
+/// * `address` - The Ethereum address.
+/// * `block` - Latest, Finalized, or a block number
+/// # Returns
+/// * `Result<CommandResponse>` - u64 (txs counts)
+/// # Errors
+/// * If the block number query fails.
+pub async fn query_transaction_count(
+    beerus: BeerusLightClient,
+    address: String,
+    block: String,
+) -> Result<CommandResponse> {
+    let block = beerus_core::ethers_helper::block_string_to_block_tag_type(block.as_str())?;
+
+    let address = Address::from_str(&address)?;
+
+    let tx_count = beerus
+        .ethereum_lightclient
+        .get_transaction_count(&address, block)
+        .await?;
+
+    Ok(CommandResponse::EthereumQueryTxCount(tx_count))
+}
+
 /// Query tx count of a given block Number
 /// # Arguments
 /// * `beerus` - The Beerus light client.
diff --git a/beerus_cli/src/model.rs b/beerus_cli/src/model.rs
index 44334d7..a81a49a 100644
--- a/beerus_cli/src/model.rs
+++ b/beerus_cli/src/model.rs
@@ -68,6 +68,15 @@ pub enum EthereumSubCommands {
         #[arg(short, long, value_name = "ADDRESS")]
         address: String,
     },
+    /// Query the transaction of an Ethereum address from the given block.
+    QueryTxCount {
+        /// The ethereum address
+        /// The block from which to query the txs count
+        #[arg(short, long, value_name = "ADDRESS")]
+        address: String,
+        #[arg(short, long, value_name = "BLOCK")]
+        block: String,
+    },
     QueryBlockTxCountByNumber {
         /// The block from which to query the txs count
         #[arg(short, long, value_name = "BLOCK")]
@@ -240,6 +249,7 @@ pub enum CommandResponse {
     EthereumQueryBlockNumber(u64),
     EthereumQueryChainId(u64),
     EthereumQueryCode(Vec<u8>),
+    EthereumQueryTxCount(u64),
     EthereumQueryBlockTxCountByNumber(u64),
     EthereumQueryBlockTxCountByHash(u64),
     EthereumQueryTxByHash(String),
@@ -295,6 +305,11 @@ impl Display for CommandResponse {
             CommandResponse::EthereumQueryCode(code) => {
                 write!(f, "{code:?}")
             }
+            // Print the transaction count of a given Ethereum address from a given block
+            // Result looks like: 123
+            CommandResponse::EthereumQueryTxCount(tx_count) => {
+                write!(f, "{tx_count:?}")
+            }
             // Print the count of txs of a block
             // Result looks like: 150
             CommandResponse::EthereumQueryBlockTxCountByNumber(tx_count) => {
diff --git a/beerus_cli/src/runner.rs b/beerus_cli/src/runner.rs
index d5f9467..fa974b0 100644
--- a/beerus_cli/src/runner.rs
+++ b/beerus_cli/src/runner.rs
@@ -44,6 +44,10 @@ pub async fn run(beerus: BeerusLightClient, cli: Cli) -> Result<CommandResponse>
             EthereumSubCommands::QueryBlockTxCountByHash { hash } => {
                 ethereum::query_block_transaction_count_by_hash(beerus, hash.to_string()).await
             }
+            EthereumSubCommands::QueryTxCount { address, block } => {
+                ethereum::query_transaction_count(beerus, address.to_string(), block.to_string())
+                    .await
+            }
             EthereumSubCommands::QueryTxByHash { hash } => {
                 ethereum::query_transaction_by_hash(beerus, hash.to_string()).await
             }
diff --git a/beerus_core/src/ethers_helper.rs b/beerus_core/src/ethers_helper.rs
index a9415fb..44b8392 100644
--- a/beerus_core/src/ethers_helper.rs
+++ b/beerus_core/src/ethers_helper.rs
@@ -3,6 +3,8 @@ use ethers::{
     abi::{Abi, AbiError, Token, Tokenize},
     types::Bytes,
 };
+use eyre::{eyre, Result};
+use helios::types::BlockTag;
 
 /// Helper for ABI encoding arguments for a specific function in a contract.
 /// # Arguments
@@ -43,11 +45,45 @@ pub fn u256_to_bytes32_type(value: U256) -> Token {
     Token::FixedBytes(u256_to_bytes32_slice(value).to_vec())
 }
 
+/// Helper converting block identifier string with corresponding type to a BlockTag Type
+/// # Arguments
+/// * `block` - The block identifier.
+/// # Returns
+/// The block identifier as BlockTag type.
+/// # Errors
+/// * If the block cannot be parsed or invalid
+pub fn block_string_to_block_tag_type(block: &str) -> Result<BlockTag> {
+    match block.to_lowercase().as_str() {
+        "finalized" => Ok(BlockTag::Finalized),
+        "latest" => Ok(BlockTag::Latest),
+        _ => match block.parse::<u64>() {
+            Ok(number) => Ok(BlockTag::Number(number)),
+            Err(_) => return Err(eyre!("Invalid BlockTag")),
+        },
+    }
+}
+
+/// Checks equality between two BlockTag inputs
+/// # Arguments
+/// * `a` - The first block identifier.
+/// * `b` - The first block identifier.
+/// # Returns
+/// bool - True or False
+pub fn block_tag_eq(a: &BlockTag, b: &BlockTag) -> bool {
+    match (a, b) {
+        (BlockTag::Latest, BlockTag::Latest) => true,
+        (BlockTag::Finalized, BlockTag::Finalized) => true,
+        (BlockTag::Number(a), BlockTag::Number(b)) => a == b,
+        _ => false,
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use std::str::FromStr;
-
+    use super::block_tag_eq;
     use ethers::types::Bytes;
+    use helios::types::BlockTag;
+    use std::str::FromStr;
 
     #[test]
     fn test_encode_function_data() {
@@ -108,4 +144,59 @@ mod tests {
             ])
         );
     }
+
+    #[test]
+    fn test_block_string_to_block_tag_type() {
+        // Testing for Number type
+        // Given
+        let block = "123".to_string();
+
+        // When
+        let result = super::block_string_to_block_tag_type(&block).unwrap();
+
+        // Then
+        let expected_result = BlockTag::Number(123);
+        let equal = block_tag_eq(&result, &expected_result);
+        assert_eq!(equal, true);
+
+        // Testing for Latest type
+        // Given
+        let block = "latest".to_string();
+
+        // When
+        let result = super::block_string_to_block_tag_type(&block).unwrap();
+
+        // Then
+        let expected_result = BlockTag::Latest;
+        let equal = block_tag_eq(&result, &expected_result);
+        assert_eq!(equal, true);
+
+        // Testing for Finalized type
+        // Given
+        let block = "finalized".to_string();
+
+        // When
+        let result = super::block_string_to_block_tag_type(&block).unwrap();
+
+        // Then
+        let expected_result = BlockTag::Finalized;
+        let equal = block_tag_eq(&result, &expected_result);
+        assert_eq!(equal, true);
+    }
+
+    #[test]
+    fn test_invalid_block_should_return_error() {
+        // Testing for invalid type
+        // Given
+        let block = "0x123".to_string();
+
+        // When
+        let result = super::block_string_to_block_tag_type(&block);
+
+        // Then
+        match result {
+            Err(e) => assert_eq!("Invalid BlockTag", e.to_string()),
+            Ok(_) => panic!("Expected error, got ok"),
+        }
+    }
 }
diff --git a/beerus_core/src/lightclient/ethereum/helios_lightclient.rs b/beerus_core/src/lightclient/ethereum/helios_lightclient.rs
index 70afc23..7488c09 100644
--- a/beerus_core/src/lightclient/ethereum/helios_lightclient.rs
+++ b/beerus_core/src/lightclient/ethereum/helios_lightclient.rs
@@ -57,6 +57,11 @@ impl EthereumLightClient for HeliosLightClient {
         self.helios_light_client.get_code(address, block).await
     }
 
+    async fn get_transaction_count(&self, address: &Address, block: BlockTag) -> Result<u64> {
+        // TODO: Rename after it has been renamed https://github.com/a16z/helios/pull/166#issuecomment-1379587761
+        self.helios_light_client.get_nonce(address, block).await
+    }
+
     async fn get_block_transaction_count_by_number(&self, block: BlockTag) -> Result<u64> {
         self.helios_light_client
             .get_block_transaction_count_by_number(block)
diff --git a/beerus_core/src/lightclient/ethereum/mod.rs b/beerus_core/src/lightclient/ethereum/mod.rs
index 3afb9f7..f96dca0 100644
--- a/beerus_core/src/lightclient/ethereum/mod.rs
+++ b/beerus_core/src/lightclient/ethereum/mod.rs
@@ -101,6 +101,16 @@ pub trait EthereumLightClient: Send + Sync {
     /// Add examples.
     async fn get_code(&self, address: &Address, block: BlockTag) -> Result<Vec<u8>>;
 
+    /// Get the txs counts of an Ethereum address from a given block.
+    /// This function should be called after `start`.
+    /// # Returns
+    /// The transaction count of a given address from a given block.
+    /// # Errors
+    /// If the call fails.
+    /// # TODO
+    /// Add examples.
+    async fn get_transaction_count(&self, address: &Address, block: BlockTag) -> Result<u64>;
+
     /// Get the txs counts of a given block number.
     /// This function should be called after `start`.
     /// # Returns
diff --git a/beerus_rest_api/src/api/ethereum/endpoints.rs b/beerus_rest_api/src/api/ethereum/endpoints.rs
index 2b596de..36668c6 100644
--- a/beerus_rest_api/src/api/ethereum/endpoints.rs
+++ b/beerus_rest_api/src/api/ethereum/endpoints.rs
@@ -3,8 +3,8 @@ use crate::api::ethereum::resp::{
     QueryBlockNumberResponse, QueryBlockTxCountByBlockHashResponse,
     QueryBlockTxCountByBlockNumberResponse, QueryChainIdResponse, QueryCodeResponse,
     QueryEstimateGasResponse, QueryGasPriceResponse, QueryLogsObject, QueryLogsResponse,
-    QueryNonceResponse, QueryPriorityFeeResponse, QueryTransactionByHashResponse, ResponseLog,
-    SendRawTransactionResponse, TransactionObject,
+    QueryNonceResponse, QueryPriorityFeeResponse, QueryTransactionByHashResponse,
+    QueryTxCountResponse, ResponseLog, SendRawTransactionResponse, TransactionObject,
 };
 use crate::api::ApiResponse;
 
@@ -74,6 +74,16 @@ pub async fn query_code(
     ApiResponse::from_result(query_code_inner(address, beerus).await)
 }
 
+#[openapi]
+#[get("/ethereum/tx_count/<address>/<block>")]
+pub async fn get_transaction_count(
+    address: &str,
+    block: &str,
+    beerus: &State<BeerusLightClient>,
+) -> ApiResponse<QueryTxCountResponse> {
+    ApiResponse::from_result(query_transaction_count_inner(address, block, beerus).await)
+}
+
 #[openapi]
 #[get("/ethereum/tx_count_by_block_number/<block>")]
 pub async fn get_block_transaction_count_by_number(
@@ -261,6 +271,29 @@ pub async fn query_code_inner(
     Ok(QueryCodeResponse { code })
 }
 
+/// Query the Tx count of a given Ethereum Address on a given Block from the the Ethereum chain.
+/// # Returns
+/// `Ok(get_transaction_count)` - u64 (tx_count)
+/// `Err(error)` - An error occurred.
+/// # Errors
+/// If the code query fails.
+/// # Examples
+pub async fn query_transaction_count_inner(
+    address: &str,
+    block: &str,
+    beerus: &State<BeerusLightClient>,
+) -> Result<QueryTxCountResponse> {
+    debug!("Querying Tx count");
+    let address = Address::from_str(address)?;
+    let block = beerus_core::ethers_helper::block_string_to_block_tag_type(block)?;
+    let tx_count = beerus
+        .ethereum_lightclient
+        .get_transaction_count(&address, block)
+        .await?;
+
+    Ok(QueryTxCountResponse { tx_count })
+}
+
 /// Query the Tx count of a given Block Number from the the Ethereum chain.
 /// # Returns
 /// `Ok(get_block_transaction_count_by_number)` - u64 (tx_count)
diff --git a/beerus_rest_api/src/api/ethereum/resp/mod.rs b/beerus_rest_api/src/api/ethereum/resp/mod.rs
index 4ca7d09..e9c322b 100644
--- a/beerus_rest_api/src/api/ethereum/resp/mod.rs
+++ b/beerus_rest_api/src/api/ethereum/resp/mod.rs
@@ -51,6 +51,12 @@ pub struct QueryCodeResponse {
     pub code: Vec<u8>,
 }
 
+#[derive(Serialize, JsonSchema)]
+#[serde(crate = "rocket::serde")]
+pub struct QueryTxCountResponse {
+    pub tx_count: u64,
+}
+
 #[derive(Serialize, JsonSchema)]
 #[serde(crate = "rocket::serde")]
 pub struct QueryBlockTxCountByBlockNumberResponse {
diff --git a/beerus_rest_api/src/lib.rs b/beerus_rest_api/src/lib.rs
index 1755bd2..c2ac2fe 100644
--- a/beerus_rest_api/src/lib.rs
+++ b/beerus_rest_api/src/lib.rs
@@ -20,6 +20,7 @@ pub async fn build_rocket_server(beerus: BeerusLightClient) -> Rocket<Build> {
             ethereum::endpoints::query_block_number,
             ethereum::endpoints::query_chain_id,
             ethereum::endpoints::query_code,
+            ethereum::endpoints::get_transaction_count,
             ethereum::endpoints::get_block_transaction_count_by_number,
             ethereum::endpoints::get_block_transaction_count_by_hash,
             ethereum::endpoints::get_transaction_by_hash,


Test patch:
diff --git a/beerus_cli/tests/cli.rs b/beerus_cli/tests/cli.rs
index 0b63a1c..a09ed6a 100644
--- a/beerus_cli/tests/cli.rs
+++ b/beerus_cli/tests/cli.rs
@@ -507,6 +507,84 @@ mod test {
         }
     }
 
+    /// Test the `query-tx-count` CLI command.
+    /// Given normal conditions, when `query-tx-count`, then ok.
+    /// Success case.
+    #[tokio::test]
+    async fn given_normal_conditions_when_query_tx_count_then_ok() {
+        let (config, mut ethereum_lightclient, starknet_lightclient) = config_and_mocks();
+
+        let expected_result: u64 = 123;
+        // Given
+        // Mock dependencies
+        ethereum_lightclient
+            .expect_get_transaction_count()
+            .return_once(move |_, _| Ok(expected_result));
+
+        let beerus = BeerusLightClient::new(
+            config,
+            Box::new(ethereum_lightclient),
+            Box::new(starknet_lightclient),
+        );
+
+        let address = "0xc24215226336d22238a20a72f8e489c005b44c4a".to_string();
+        let block = "latest".to_string();
+
+        // Mock the command line arguments.
+        let cli = Cli {
+            config: None,
+            command: Commands::Ethereum(EthereumCommands {
+                command: EthereumSubCommands::QueryTxCount { address, block },
+            }),
+        };
+
+        let result = runner::run(beerus, cli).await.unwrap();
+
+        assert_eq!("123", result.to_string());
+    }
+
+    /// Test `query-tx-count` CLI command.
+    /// Given ethereum lightclient returns an error, when `query-tx-count`, then the error is propagated.
+    /// Error case.
+    #[tokio::test]
+    async fn given_ethereum_lightclient_returns_error_when_query_tx_count_then_error_is_propagated()
+    {
+        // Build mocks.
+        let (config, mut ethereum_lightclient, starknet_lightclient) = config_and_mocks();
+
+        // Given
+        // Mock dependencies.
+        ethereum_lightclient
+            .expect_get_transaction_count()
+            .return_once(move |_, _| Err(eyre::eyre!("ethereum_lightclient_error")));
+
+        let beerus = BeerusLightClient::new(
+            config,
+            Box::new(ethereum_lightclient),
+            Box::new(starknet_lightclient),
+        );
+
+        let address = "0xc24215226336d22238a20a72f8e489c005b44c4a".to_string();
+        let block = "latest".to_string();
+
+        // Mock the command line arguments.
+        let cli = Cli {
+            config: None,
+            command: Commands::Ethereum(EthereumCommands {
+                command: EthereumSubCommands::QueryTxCount { address, block },
+            }),
+        };
+
+        // When
+        let result = runner::run(beerus, cli).await;
+
+        // Then
+        match result {
+            Err(e) => assert_eq!("ethereum_lightclient_error", e.to_string()),
+            Ok(_) => panic!("Expected error,got ok"),
+        }
+    }
+
     /// Test the `query_block_tx_count_by_hash` CLI command.
     /// Given normal conditions, when `query_block_tx_count_by_hash`, then ok.
     /// Success case.
diff --git a/beerus_cli/tests/model.rs b/beerus_cli/tests/model.rs
index 857f9c9..54e09d2 100644
--- a/beerus_cli/tests/model.rs
+++ b/beerus_cli/tests/model.rs
@@ -14,6 +14,12 @@ mod tests {
         assert_eq!(response.to_string(), "1");
     }
 
+    #[test]
+    fn test_display_ethereum_query_tx_count() {
+        let response = CommandResponse::EthereumQueryTxCount(1);
+        assert_eq!(response.to_string(), "1");
+    }
+
     #[test]
     fn test_display_starknet_query_state_root() {
         let response = CommandResponse::StarkNetQueryStateRoot(1.into());
diff --git a/beerus_core/tests/beerus.rs b/beerus_core/tests/beerus.rs
index 4a4d47c..b62ec00 100644
--- a/beerus_core/tests/beerus.rs
+++ b/beerus_core/tests/beerus.rs
@@ -487,6 +487,85 @@ mod tests {
         // Assert that the error returned by the `get_code` method of the Beerus light client is the expected error.
         assert_eq!(result.unwrap_err().to_string(), expected_error.to_string());
     }
+
+    /// Test the `get_transaction_count` method when everything is fine.
+    /// This test mocks external dependencies.
+    /// It does not test the `get_transaction_count` method of the external dependencies.
+    /// It tests the `get_transaction_count` method of the Beerus light client.    
+    #[tokio::test]
+    async fn given_normal_conditions_when_query_tx_count_then_ok() {
+        let (config, mut ethereum_lightclient_mock, starknet_lightclient_mock) = mock_clients();
+
+        // Mock the `get_transaction_count` method of the Ethereum light client.
+        let expected_result: u64 = 120;
+        ethereum_lightclient_mock
+            .expect_get_transaction_count()
+            .return_once(move |_, _| Ok(expected_result));
+        let beerus = BeerusLightClient::new(
+            config.clone(),
+            Box::new(ethereum_lightclient_mock),
+            Box::new(starknet_lightclient_mock),
+        );
+
+        // Prepare variables
+        let address = Address::from_str("0xc24215226336d22238a20a72f8e489c005b44c4a").unwrap();
+        let block = BlockTag::Latest;
+
+        // When
+        let result = beerus
+            .ethereum_lightclient
+            .get_transaction_count(&address, block)
+            .await;
+
+        // Then
+        // Assert that the `get_transaction_count` method of the Beerus light client returns `Ok`.
+        assert!(result.is_ok());
+        // Assert that the code returned byt `get_transaction_count` method of the Beerus light client is the expected code.
+        assert_eq!(result.unwrap(), 120);
+    }
+
+    /// Test the `get_transaction_count` method when the Ethereum light client returns an error.
+    /// This test mocks external dependencies.
+    /// It does not test the `get_transaction_count` method of the external dependencies.
+    /// It tests the `get_transaction_count` method of the Beerus light client.
+    /// It tests the error handling of the `start` method of the Beerus light client.
+    #[tokio::test]
+    async fn giver_ethereum_lightclient_returns_error_when_query_tx_count_then_error_is_propagated()
+    {
+        // Given
+        // Mock config, ethereum light client and starknet light client.
+        let (config, mut ethereum_lightclient_mock, starknet_lightclient_mock) = mock_clients();
+
+        let expected_error = "ethereum_lightclient_error";
+
+        // Mock dependencies.
+        ethereum_lightclient_mock
+            .expect_get_transaction_count()
+            .return_once(move |_, _| Err(eyre::eyre!("ethereum_lightclient_error")));
+
+        // When
+        let beerus = BeerusLightClient::new(
+            config.clone(),
+            Box::new(ethereum_lightclient_mock),
+            Box::new(starknet_lightclient_mock),
+        );
+
+        let address = Address::from_str("0xc24215226336d22238a20a72f8e489c005b44c4a").unwrap();
+        let block = BlockTag::Latest;
+
+        // Query the transaction of the Ethereum address from a given block.
+        let result = beerus
+            .ethereum_lightclient
+            .get_transaction_count(&address, block)
+            .await;
+
+        // Then
+        // Assert that the `get_transaction_count` method of the Beerus light client returns `Err`.
+        assert!(result.is_err());
+        // Assert that the error returned by the `get_transaction_count` method of the Beerus light client is the expected error.
+        assert_eq!(result.unwrap_err().to_string(), expected_error.to_string());
+    }
+
     /// Test the `get_block_transaction_count_by_number` method when everything is fine.
     /// This test mocks external dependencies.
     /// It does not test the `get_block_transaction_count_by_number` method of the external dependencies.
diff --git a/beerus_rest_api/tests/api.rs b/beerus_rest_api/tests/api.rs
index 8b02e2a..3b137f1 100644
--- a/beerus_rest_api/tests/api.rs
+++ b/beerus_rest_api/tests/api.rs
@@ -550,6 +550,81 @@ mod test {
         )
     }
 
+    /// Test the `query_tx_count` endpoint.
+    /// `/ethereum/query_tx_count/<address>/<block>`
+    /// Given normal conditions, when `query_tx_count`, then ok.
+    #[tokio::test]
+    async fn given_normal_conditions_when_query_tx_count_then_ok() {
+        // Build mocks
+        let (config, mut ethereum_lightclient, starknet_lightclient) = config_and_mocks();
+
+        let expected_result: u64 = 120;
+        // Given
+        // Mock dependencies
+        ethereum_lightclient
+            .expect_get_transaction_count()
+            .return_once(move |_, _| Ok(expected_result));
+
+        let beerus = BeerusLightClient::new(
+            config,
+            Box::new(ethereum_lightclient),
+            Box::new(starknet_lightclient),
+        );
+
+        let client = Client::tracked(build_rocket_server(beerus).await)
+            .await
+            .expect("valid rocket instance");
+
+        let response = client
+            .get(uri!(
+                "/ethereum/tx_count/0xc24215226336d22238a20a72f8e489c005b44c4a/latest"
+            ))
+            .dispatch()
+            .await;
+
+        assert_eq!(response.status(), Status::Ok);
+        assert_eq!(response.into_string().await.unwrap(), "{\"tx_count\":120}")
+    }
+
+    /// Test the `query_tx_count` endpoint.
+    /// `/ethereum/query_tx_count/<address>/<block>`
+    /// Given Ethereum light client returns error when `query_tx_count`, then error is propagated.
+    #[tokio::test]
+    async fn given_ethereum_lightclient_returns_error_when_query_tx_count_then_error_is_propagated()
+    {
+        // Build mocks
+        let (config, mut ethereum_lightclient, starknet_lightclient) = config_and_mocks();
+
+        // Given
+        // Mock dependencies
+        ethereum_lightclient
+            .expect_get_transaction_count()
+            .return_once(move |_, _| Err(eyre::eyre!("Cannot query tx count")));
+
+        let beerus = BeerusLightClient::new(
+            config,
+            Box::new(ethereum_lightclient),
+            Box::new(starknet_lightclient),
+        );
+
+        let client = Client::tracked(build_rocket_server(beerus).await)
+            .await
+            .expect("valid rocket instance");
+
+        let response = client
+            .get(uri!(
+                "/ethereum/tx_count/0xc24215226336d22238a20a72f8e489c005b44c4a/latest"
+            ))
+            .dispatch()
+            .await;
+
+        assert_eq!(response.status(), Status::InternalServerError);
+        assert_eq!(
+            response.into_string().await.unwrap(),
+            "{\"error_message\":\"Cannot query tx count\"}"
+        )
+    }
+
     /// Test the `query_block_transaction_count_by_number` endpoint.
     /// `/ethereum/tx_count_by_block_number`
     /// Given normal conditions, when `query_block_transaction_count_by_number`, then ok.