diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md
index 8b125fd0..8d8f3848 100644
--- a/SUPPORTED_APIS.md
+++ b/SUPPORTED_APIS.md
@@ -33,8 +33,8 @@ The `status` options are:
| [`ETH`](#eth-namespace) | [`eth_getBalance`](#eth_getbalance) | `SUPPORTED` | Returns the balance of the account of given address |
| [`ETH`](#eth-namespace) | [`eth_getBlockByHash`](#eth_getblockbyhash) | `SUPPORTED` | Returns information about a block by block hash |
| [`ETH`](#eth-namespace) | [`eth_getBlockByNumber`](#eth_getblockbynumber) | `SUPPORTED` | Returns information about a block by block number |
-| `ETH` | `eth_getBlockTransactionCountByHash` | `NOT IMPLEMENTED` [GitHub Issue #44](https://github.com/matter-labs/era-test-node/issues/44) | Number of transactions in a block from a block matching the given block hash |
-| `ETH` | `eth_getBlockTransactionCountByNumber` | `NOT IMPLEMENTED` [GitHub Issue #43](https://github.com/matter-labs/era-test-node/issues/43) | Number of transactions in a block from a block matching the given block number |
+| [`ETH`](#eth-namespace) | [`eth_getBlockTransactionCountByHash`](#eth_getblocktransactioncountbyhash) | `SUPPORTED` | Number of transactions in a block from a block matching the given block hash |
+| [`ETH`](#eth-namespace) | [`eth_getBlockTransactionCountByNumber`](#eth_getblocktransactioncountbynumber) | `SUPPORTED` | Number of transactions in a block from a block matching the given block number |
| `ETH` | `eth_getCompilers` | `NOT IMPLEMENTED` | Returns a list of available compilers |
| [`ETH`](#eth-namespace) | [`eth_getTransactionByHash`](#eth_gettransactionbyhash) | `SUPPORTED` | Returns the information about a transaction requested by transaction hash |
| [`ETH`](#eth-namespace) | [`eth_getTransactionCount`](#eth_gettransactioncount) | `SUPPORTED` | Returns the number of transactions sent from an address |
@@ -512,6 +512,62 @@ curl --request POST \
}'
```
+### `eth_getBlockTransactionCountByHash`
+
+[source](src/node.rs)
+
+Number of transactions in a block from a block matching the given block hash
+
+#### Arguments
+
++ `block_hash: H256`
+
+#### Status
+
+`SUPPORTED`
+
+#### Example
+
+```bash
+curl --request POST \
+ --url http://localhost:8011/ \
+ --header 'content-type: application/json' \
+ --data '{
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": "eth_getBlockTransactionCountByHash",
+ "params": ["0x0000000000000000000000000000000000000000000000000000000000000008"]
+}'
+```
+
+### `eth_getBlockTransactionCountByNumber`
+
+[source](src/node.rs)
+
+Number of transactions in a block from a block matching the given block number
+
+#### Arguments
+
++ `block_number: BlockNumber`
+
+#### Status
+
+`SUPPORTED`
+
+#### Example
+
+```bash
+curl --request POST \
+ --url http://localhost:8011/ \
+ --header 'content-type: application/json' \
+ --data '{
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": "eth_getBlockTransactionCountByNumber",
+ "params": ["latest"]
+}'
+```
+
### `eth_getCode`
[source](src/node.rs)
diff --git a/src/fork.rs b/src/fork.rs
index 752448bd..a5346561 100644
--- a/src/fork.rs
+++ b/src/fork.rs
@@ -221,7 +221,16 @@ pub trait ForkSource {
&self,
block_number: zksync_types::api::BlockNumber,
full_transactions: bool,
- ) -> eyre::Result>>;
+ ) -> eyre::Result >>;
+
+ /// Returns the transaction count for a given block hash.
+ fn get_block_transaction_count_by_hash(&self, block_hash: H256) -> eyre::Result >;
+
+ /// Returns the transaction count for a given block number.
+ fn get_block_transaction_count_by_number(
+ &self,
+ block_number: zksync_types::api::BlockNumber,
+ ) -> eyre::Result >;
}
/// Holds the information about the original chain.
diff --git a/src/http_fork_source.rs b/src/http_fork_source.rs
index cc38c9e6..fa0221ce 100644
--- a/src/http_fork_source.rs
+++ b/src/http_fork_source.rs
@@ -1,6 +1,7 @@
use std::sync::RwLock;
use eyre::Context;
+use zksync_basic_types::{H256, U256};
use zksync_web3_decl::{
jsonrpsee::http_client::{HttpClient, HttpClientBuilder},
namespaces::{EthNamespaceClient, ZksNamespaceClient},
@@ -198,6 +199,27 @@ impl ForkSource for HttpForkSource {
})
.wrap_err("fork http client failed")
}
+
+ /// Returns the transaction count for a given block hash.
+ fn get_block_transaction_count_by_hash(&self, block_hash: H256) -> eyre::Result > {
+ let client = self.create_client();
+ block_on(async move { client.get_block_transaction_count_by_hash(block_hash).await })
+ .wrap_err("fork http client failed")
+ }
+
+ /// Returns the transaction count for a given block number.
+ fn get_block_transaction_count_by_number(
+ &self,
+ block_number: zksync_types::api::BlockNumber,
+ ) -> eyre::Result > {
+ let client = self.create_client();
+ block_on(async move {
+ client
+ .get_block_transaction_count_by_number(block_number)
+ .await
+ })
+ .wrap_err("fork http client failed")
+ }
}
#[cfg(test)]
diff --git a/src/node.rs b/src/node.rs
index 18b04bf5..ba6cf4b7 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -1832,16 +1832,91 @@ impl EthNamespaceT for
fn get_block_transaction_count_by_number(
&self,
- _block_number: zksync_types::api::BlockNumber,
+ block_number: zksync_types::api::BlockNumber,
) -> jsonrpc_core::BoxFuture>> {
- not_implemented("get_block_transaction_count_by_number")
+ let inner = Arc::clone(&self.inner);
+
+ Box::pin(async move {
+ let maybe_result = {
+ let reader = match inner.read() {
+ Ok(r) => r,
+ Err(_) => return Err(into_jsrpc_error(Web3Error::InternalError)),
+ };
+ let number = match block_number {
+ zksync_types::api::BlockNumber::Latest
+ | zksync_types::api::BlockNumber::Pending
+ | zksync_types::api::BlockNumber::Finalized
+ | zksync_types::api::BlockNumber::Committed => reader.current_miniblock,
+ zksync_types::api::BlockNumber::Number(ask_number) => ask_number.as_u64(),
+ zksync_types::api::BlockNumber::Earliest => 0,
+ };
+
+ reader
+ .block_hashes
+ .get(&number)
+ .and_then(|hash| reader.blocks.get(hash))
+ .map(|block| U256::from(block.transactions.len()))
+ .or_else(|| {
+ reader
+ .fork_storage
+ .inner
+ .read()
+ .expect("failed reading fork storage")
+ .fork
+ .as_ref()
+ .and_then(|fork| {
+ fork.fork_source
+ .get_block_transaction_count_by_number(block_number)
+ .ok()
+ .flatten()
+ })
+ })
+ };
+
+ match maybe_result {
+ Some(value) => Ok(Some(value)),
+ None => Err(into_jsrpc_error(Web3Error::NoBlock)),
+ }
+ })
}
fn get_block_transaction_count_by_hash(
&self,
- _block_hash: zksync_basic_types::H256,
+ block_hash: zksync_basic_types::H256,
) -> jsonrpc_core::BoxFuture>> {
- not_implemented("get_block_transaction_count_by_hash")
+ let inner = Arc::clone(&self.inner);
+
+ Box::pin(async move {
+ let reader = inner
+ .read()
+ .map_err(|_| into_jsrpc_error(Web3Error::InternalError))?;
+
+ // try retrieving block from memory, and if unavailable subsequently from the fork
+ let maybe_result = reader
+ .blocks
+ .get(&block_hash)
+ .map(|block| U256::from(block.transactions.len()))
+ .or_else(|| {
+ reader
+ .fork_storage
+ .inner
+ .read()
+ .expect("failed reading fork storage")
+ .fork
+ .as_ref()
+ .and_then(|fork| {
+ fork.fork_source
+ .get_block_transaction_count_by_hash(block_hash)
+ .ok()
+ .flatten()
+ })
+ });
+
+ match maybe_result {
+ Some(value) => Ok(Some(value)),
+ None => Err(into_jsrpc_error(Web3Error::NoBlock)),
+ }
+ })
}
fn get_storage(
@@ -1931,9 +2006,12 @@ impl EthNamespaceT for
#[cfg(test)]
mod tests {
use crate::{
- cache::CacheConfig, http_fork_source::HttpForkSource, node::InMemoryNode, testing,
+ cache::CacheConfig,
+ http_fork_source::HttpForkSource,
+ node::InMemoryNode,
+ testing::{self, ForkBlockConfig, MockServer},
};
- use zksync_types::{api::BlockNumber, Address, L2ChainId, Nonce, PackedEthSignature};
+ use zksync_types::api::BlockNumber;
use zksync_web3_decl::types::SyncState;
use super::*;
@@ -1972,35 +2050,8 @@ mod tests {
#[tokio::test]
async fn test_get_block_by_hash_for_produced_block() {
let node = InMemoryNode::::default();
+ let expected_block_hash = testing::apply_tx(&node, H256::repeat_byte(0x01));
- let private_key = H256::random();
- let from_account = PackedEthSignature::address_from_private_key(&private_key)
- .expect("failed generating address");
- node.set_rich_account(from_account);
- let mut tx = L2Tx::new_signed(
- Address::random(),
- vec![],
- Nonce(0),
- Fee {
- gas_limit: U256::from(1_000_000),
- max_fee_per_gas: U256::from(250_000_000),
- max_priority_fee_per_gas: U256::from(250_000_000),
- gas_per_pubdata_limit: U256::from(20000),
- },
- U256::from(1),
- L2ChainId(260),
- &private_key,
- None,
- Default::default(),
- )
- .unwrap();
- tx.set_input(vec![], H256::repeat_byte(0x01));
-
- node.apply_txs(vec![tx.into()]).expect("failed applying tx");
-
- let expected_block_hash =
- H256::from_str("0x89c0aa770eba1f187235bdad80de9c01fe81bca415d442ca892f087da56fa109")
- .unwrap();
let actual_block = node
.get_block_by_hash(expected_block_hash, false)
.await
@@ -2016,8 +2067,11 @@ mod tests {
async fn test_node_block_mapping_is_correctly_populated_when_using_fork_source() {
let input_block_number = 8;
let input_block_hash = H256::repeat_byte(0x01);
- let mock_server =
- testing::MockServer::run_with_config(input_block_number, input_block_hash);
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: input_block_number,
+ hash: input_block_hash,
+ transaction_count: 0,
+ });
let node = InMemoryNode::::new(
Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
@@ -2044,7 +2098,11 @@ mod tests {
async fn test_get_block_by_hash_uses_fork_source() {
let input_block_hash = H256::repeat_byte(0x01);
- let mock_server = testing::MockServer::run_with_config(10, H256::repeat_byte(0xab));
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
let mock_block_number = 8;
let block_response = testing::BlockResponseBuilder::new()
.set_hash(input_block_hash)
@@ -2098,33 +2156,9 @@ mod tests {
#[tokio::test]
async fn test_get_block_by_number_for_produced_block() {
let node = InMemoryNode::::default();
-
- let private_key = H256::random();
- let from_account = PackedEthSignature::address_from_private_key(&private_key)
- .expect("failed generating address");
- node.set_rich_account(from_account);
- let mut tx = L2Tx::new_signed(
- Address::random(),
- vec![],
- Nonce(0),
- Fee {
- gas_limit: U256::from(1_000_000),
- max_fee_per_gas: U256::from(250_000_000),
- max_priority_fee_per_gas: U256::from(250_000_000),
- gas_per_pubdata_limit: U256::from(20000),
- },
- U256::from(1),
- L2ChainId(260),
- &private_key,
- None,
- Default::default(),
- )
- .unwrap();
- tx.set_input(vec![], H256::repeat_byte(0x01));
-
- node.apply_txs(vec![tx.into()]).expect("failed applying tx");
-
+ testing::apply_tx(&node, H256::repeat_byte(0x01));
let expected_block_number = 1;
+
let actual_block = node
.get_block_by_number(BlockNumber::Number(U64::from(expected_block_number)), false)
.await
@@ -2137,7 +2171,11 @@ mod tests {
#[tokio::test]
async fn test_get_block_by_number_uses_fork_source_if_missing_number() {
- let mock_server = testing::MockServer::run_with_config(10, H256::repeat_byte(0xab));
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
let mock_block_number = 8;
let block_response = testing::BlockResponseBuilder::new()
.set_number(mock_block_number)
@@ -2175,33 +2213,9 @@ mod tests {
#[tokio::test]
async fn test_get_block_by_number_for_latest_block_produced_locally() {
let node = InMemoryNode::::default();
-
- let private_key = H256::random();
- let from_account = PackedEthSignature::address_from_private_key(&private_key)
- .expect("failed generating address");
- node.set_rich_account(from_account);
- let mut tx = L2Tx::new_signed(
- Address::random(),
- vec![],
- Nonce(0),
- Fee {
- gas_limit: U256::from(1_000_000),
- max_fee_per_gas: U256::from(250_000_000),
- max_priority_fee_per_gas: U256::from(250_000_000),
- gas_per_pubdata_limit: U256::from(20000),
- },
- U256::from(1),
- L2ChainId(260),
- &private_key,
- None,
- Default::default(),
- )
- .unwrap();
- tx.set_input(vec![], H256::repeat_byte(0x01));
-
- node.apply_txs(vec![tx.into()]).expect("failed applying tx");
-
+ testing::apply_tx(&node, H256::repeat_byte(0x01));
let latest_block_number = 1;
+
let actual_block = node
.get_block_by_number(BlockNumber::Latest, true)
.await
@@ -2215,8 +2229,12 @@ mod tests {
#[tokio::test]
async fn test_get_block_by_number_uses_locally_available_block_for_latest_block() {
let input_block_number = 10;
- let mock_server =
- testing::MockServer::run_with_config(input_block_number, H256::repeat_byte(0xab));
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: input_block_number,
+ hash: H256::repeat_byte(0x01),
+ transaction_count: 0,
+ });
+
let node = InMemoryNode::::new(
Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
crate::node::ShowCalls::None,
@@ -2237,7 +2255,11 @@ mod tests {
#[tokio::test]
async fn test_get_block_by_number_uses_fork_source_for_earliest_block() {
- let mock_server = testing::MockServer::run_with_config(10, H256::repeat_byte(0xab));
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
let input_block_number = 1;
mock_server.expect(
serde_json::json!({
@@ -2279,8 +2301,11 @@ mod tests {
BlockNumber::Finalized,
] {
let input_block_number = 10;
- let mock_server =
- testing::MockServer::run_with_config(input_block_number, H256::repeat_byte(0xab));
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: input_block_number,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
let node = InMemoryNode::::new(
Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
crate::node::ShowCalls::None,
@@ -2304,4 +2329,211 @@ mod tests {
);
}
}
+
+ #[tokio::test]
+ async fn test_get_block_transaction_count_by_hash_for_produced_block() {
+ let node = InMemoryNode::::default();
+
+ let expected_block_hash = testing::apply_tx(&node, H256::repeat_byte(0x01));
+ let actual_transaction_count = node
+ .get_block_transaction_count_by_hash(expected_block_hash)
+ .await
+ .expect("failed fetching block by hash")
+ .expect("no result");
+
+ assert_eq!(U256::from(1), actual_transaction_count);
+ }
+
+ #[tokio::test]
+ async fn test_get_block_transaction_count_by_hash_uses_fork_source() {
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
+ let input_block_hash = H256::repeat_byte(0x01);
+ let input_transaction_count = 1;
+ mock_server.expect(
+ serde_json::json!({
+ "jsonrpc": "2.0",
+ "id": 0,
+ "method": "eth_getBlockTransactionCountByHash",
+ "params": [
+ format!("{:#x}", input_block_hash),
+ ],
+ }),
+ serde_json::json!({
+ "jsonrpc": "2.0",
+ "id": 0,
+ "result": format!("{:#x}", input_transaction_count),
+ }),
+ );
+ let node = InMemoryNode::::new(
+ Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
+ crate::node::ShowCalls::None,
+ ShowStorageLogs::None,
+ ShowVMDetails::None,
+ ShowGasDetails::None,
+ false,
+ &system_contracts::Options::BuiltIn,
+ );
+
+ let actual_transaction_count = node
+ .get_block_transaction_count_by_hash(input_block_hash)
+ .await
+ .expect("failed fetching block by hash")
+ .expect("no result");
+
+ assert_eq!(
+ U256::from(input_transaction_count),
+ actual_transaction_count
+ );
+ }
+
+ #[tokio::test]
+ async fn test_get_block_transaction_count_by_number_for_produced_block() {
+ let node = InMemoryNode::::default();
+
+ testing::apply_tx(&node, H256::repeat_byte(0x01));
+ let actual_transaction_count = node
+ .get_block_transaction_count_by_number(BlockNumber::Number(U64::from(1)))
+ .await
+ .expect("failed fetching block by hash")
+ .expect("no result");
+
+ assert_eq!(U256::from(1), actual_transaction_count);
+ }
+
+ #[tokio::test]
+ async fn test_get_block_transaction_count_by_number_uses_fork_source() {
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
+ let input_block_number = 1;
+ let input_transaction_count = 1;
+ mock_server.expect(
+ serde_json::json!({
+ "jsonrpc": "2.0",
+ "id": 0,
+ "method": "eth_getBlockTransactionCountByNumber",
+ "params": [
+ format!("{:#x}", input_block_number),
+ ],
+ }),
+ serde_json::json!({
+ "jsonrpc": "2.0",
+ "id": 0,
+ "result": format!("{:#x}", input_transaction_count),
+ }),
+ );
+
+ let node = InMemoryNode::::new(
+ Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
+ crate::node::ShowCalls::None,
+ ShowStorageLogs::None,
+ ShowVMDetails::None,
+ ShowGasDetails::None,
+ false,
+ &system_contracts::Options::BuiltIn,
+ );
+
+ let actual_transaction_count = node
+ .get_block_transaction_count_by_number(BlockNumber::Number(U64::from(1)))
+ .await
+ .expect("failed fetching block by hash")
+ .expect("no result");
+
+ assert_eq!(
+ U256::from(input_transaction_count),
+ actual_transaction_count
+ );
+ }
+
+ #[tokio::test]
+ async fn test_get_block_transaction_count_by_number_earliest_uses_fork_source() {
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ hash: H256::repeat_byte(0xab),
+ transaction_count: 0,
+ });
+ let input_transaction_count = 1;
+ mock_server.expect(
+ serde_json::json!({
+ "jsonrpc": "2.0",
+ "id": 0,
+ "method": "eth_getBlockTransactionCountByNumber",
+ "params": [
+ "earliest",
+ ],
+ }),
+ serde_json::json!({
+ "jsonrpc": "2.0",
+ "id": 0,
+ "result": format!("{:#x}", input_transaction_count),
+ }),
+ );
+
+ let node = InMemoryNode::::new(
+ Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
+ crate::node::ShowCalls::None,
+ ShowStorageLogs::None,
+ ShowVMDetails::None,
+ ShowGasDetails::None,
+ false,
+ &system_contracts::Options::BuiltIn,
+ );
+
+ let actual_transaction_count = node
+ .get_block_transaction_count_by_number(BlockNumber::Earliest)
+ .await
+ .expect("failed fetching block by hash")
+ .expect("no result");
+
+ assert_eq!(
+ U256::from(input_transaction_count),
+ actual_transaction_count
+ );
+ }
+
+ #[tokio::test]
+ async fn test_get_block_transaction_count_by_number_latest_alike_uses_fork_source() {
+ for block_number in [
+ BlockNumber::Latest,
+ BlockNumber::Pending,
+ BlockNumber::Committed,
+ BlockNumber::Finalized,
+ ] {
+ let input_transaction_count = 1;
+ let mock_server = MockServer::run_with_config(ForkBlockConfig {
+ number: 10,
+ transaction_count: input_transaction_count,
+ hash: H256::repeat_byte(0xab),
+ });
+
+ let node = InMemoryNode::::new(
+ Some(ForkDetails::from_network(&mock_server.url(), None, CacheConfig::None).await),
+ crate::node::ShowCalls::None,
+ ShowStorageLogs::None,
+ ShowVMDetails::None,
+ ShowGasDetails::None,
+ false,
+ &system_contracts::Options::BuiltIn,
+ );
+
+ let actual_transaction_count = node
+ .get_block_transaction_count_by_number(block_number)
+ .await
+ .expect("failed fetching block by hash")
+ .expect("no result");
+
+ assert_eq!(
+ U256::from(input_transaction_count),
+ actual_transaction_count,
+ "case {}",
+ block_number,
+ );
+ }
+ }
}
diff --git a/src/testing.rs b/src/testing.rs
index c061d633..24f6dc8c 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -5,13 +5,25 @@
#![cfg(test)]
+use crate::node::InMemoryNode;
+use crate::{fork::ForkSource, node::compute_hash};
+
use httptest::{
matchers::{eq, json_decoded, request},
responders::json_encoded,
Expectation, Server,
};
use itertools::Itertools;
-use zksync_basic_types::H256;
+use std::str::FromStr;
+use zksync_types::{fee::Fee, l2::L2Tx, Address, L2ChainId, Nonce, PackedEthSignature, H256, U256};
+
+/// Configuration for the [MockServer]'s initial block.
+#[derive(Default, Debug, Clone)]
+pub struct ForkBlockConfig {
+ pub number: u64,
+ pub hash: H256,
+ pub transaction_count: u8,
+}
/// A HTTP server that can be used to mock a fork source.
pub struct MockServer {
@@ -28,8 +40,8 @@ impl MockServer {
}
/// Start the mock server with pre-defined calls used to fetch the fork's state.
- /// The input can be used to set the initial block's number and hash.
- pub fn run_with_config(block_number: u64, block_hash: H256) -> Self {
+ /// The input config can be used to set the initial block's number, hash and transactions.
+ pub fn run_with_config(block_config: ForkBlockConfig) -> Self {
let server = Server::run();
// setup initial fork calls
@@ -42,7 +54,7 @@ impl MockServer {
.respond_with(json_encoded(serde_json::json!({
"jsonrpc": "2.0",
"id": 0,
- "result": format!("{:#x}", block_number),
+ "result": format!("{:#x}", block_config.number),
}))),
);
server.expect(
@@ -50,18 +62,18 @@ impl MockServer {
"jsonrpc": "2.0",
"id": 1,
"method": "zks_getBlockDetails",
- "params": [ block_number ],
+ "params": [ block_config.number ],
})))))
.respond_with(json_encoded(serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"result": {
- "number": block_number,
+ "number": block_config.number,
"l1BatchNumber": 1,
"timestamp": 1676461082u64,
"l1TxCount": 0,
"l2TxCount": 0,
- "rootHash": format!("{:#x}", block_hash),
+ "rootHash": format!("{:#x}", block_config.hash),
"status": "verified",
"commitTxHash": "0x9f5b07e968787514667fae74e77ecab766be42acd602c85cfdbda1dc3dd9902f",
"committedAt": "2023-02-15T11:40:39.326104Z",
@@ -85,20 +97,20 @@ impl MockServer {
"jsonrpc": "2.0",
"id": 2,
"method": "eth_getBlockByHash",
- "params": [format!("{:#x}", block_hash), true],
+ "params": [format!("{:#x}", block_config.hash), true],
}))))).times(0..)
.respond_with(json_encoded(serde_json::json!({
"jsonrpc": "2.0",
"id": 2,
"result": {
- "hash": format!("{:#x}", block_hash),
+ "hash": format!("{:#x}", block_config.hash),
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"miner": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "number": format!("{:#x}", block_number),
+ "number": format!("{:#x}", block_config.number),
"l1BatchNumber": "0x6",
"gasUsed": "0x0",
"gasLimit": "0xffffffff",
@@ -111,7 +123,14 @@ impl MockServer {
"totalDifficulty": "0x0",
"sealFields": [],
"uncles": [],
- "transactions": [],
+ "transactions": (0..block_config.transaction_count)
+ .into_iter()
+ .map(|index| {
+ TransactionResponseBuilder::new()
+ .set_hash(H256::repeat_byte(index))
+ .build_result()
+ })
+ .collect::>(),
"size": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000"
@@ -174,37 +193,42 @@ impl BlockResponseBuilder {
self
}
+ /// Builds the block json result response
+ pub fn build_result(&mut self) -> serde_json::Value {
+ serde_json::json!({
+ "hash": format!("{:#x}", self.hash),
+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "miner": "0x0000000000000000000000000000000000000000",
+ "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "number": format!("{:#x}", self.number),
+ "l1BatchNumber": "0x6",
+ "gasUsed": "0x0",
+ "gasLimit": "0xffffffff",
+ "baseFeePerGas": "0x1dcd6500",
+ "extraData": "0x",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "timestamp": "0x63ecc41a",
+ "l1BatchTimestamp": "0x63ecbd12",
+ "difficulty": "0x0",
+ "totalDifficulty": "0x0",
+ "sealFields": [],
+ "uncles": [],
+ "transactions": [],
+ "size": "0x0",
+ "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "nonce": "0x0000000000000000"
+ })
+ }
+
/// Builds the json response
pub fn build(&mut self) -> serde_json::Value {
serde_json::json!({
"jsonrpc": "2.0",
"id": 0,
- "result": {
- "hash": format!("{:#x}", self.hash),
- "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
- "miner": "0x0000000000000000000000000000000000000000",
- "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "number": format!("{:#x}", self.number),
- "l1BatchNumber": "0x6",
- "gasUsed": "0x0",
- "gasLimit": "0xffffffff",
- "baseFeePerGas": "0x1dcd6500",
- "extraData": "0x",
- "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- "timestamp": "0x63ecc41a",
- "l1BatchTimestamp": "0x63ecbd12",
- "difficulty": "0x0",
- "totalDifficulty": "0x0",
- "sealFields": [],
- "uncles": [],
- "transactions": [],
- "size": "0x0",
- "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "nonce": "0x0000000000000000"
- },
+ "result": self.build_result(),
})
}
}
@@ -227,30 +251,35 @@ impl TransactionResponseBuilder {
self
}
+ /// Builds the transaction json result
+ pub fn build_result(&mut self) -> serde_json::Value {
+ serde_json::json!({
+ "hash": format!("{:#x}", self.hash),
+ "nonce": "0x0",
+ "blockHash": "0x51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f",
+ "blockNumber": "0x1",
+ "transactionIndex": "0x0",
+ "from": "0x29df43f75149d0552475a6f9b2ac96e28796ed0b",
+ "to": "0x0000000000000000000000000000000000008006",
+ "value": "0x0",
+ "gasPrice": "0x0",
+ "gas": "0x44aa200",
+ "input": "0x3cda33510000000000000000000000000000000000000000000000000000000000000000010000553109a66f1432eb2286c54694784d1b6993bc24a168be0a49b4d0fd4500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
+ "type": "0xff",
+ "maxFeePerGas": "0x0",
+ "maxPriorityFeePerGas": "0x0",
+ "chainId": "0x144",
+ "l1BatchNumber": "0x1",
+ "l1BatchTxIndex": "0x0",
+ })
+ }
+
/// Builds the json response
pub fn build(&mut self) -> serde_json::Value {
serde_json::json!({
"jsonrpc": "2.0",
"id": 0,
- "result": {
- "hash": format!("{:#x}", self.hash),
- "nonce": "0x0",
- "blockHash": "0x51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f",
- "blockNumber": "0x1",
- "transactionIndex": "0x0",
- "from": "0x29df43f75149d0552475a6f9b2ac96e28796ed0b",
- "to": "0x0000000000000000000000000000000000008006",
- "value": "0x0",
- "gasPrice": "0x0",
- "gas": "0x44aa200",
- "input": "0x3cda33510000000000000000000000000000000000000000000000000000000000000000010000553109a66f1432eb2286c54694784d1b6993bc24a168be0a49b4d0fd4500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
- "type": "0xff",
- "maxFeePerGas": "0x0",
- "maxPriorityFeePerGas": "0x0",
- "chainId": "0x144",
- "l1BatchNumber": "0x1",
- "l1BatchTxIndex": "0x0",
- },
+ "result": self.build_result(),
})
}
}
@@ -273,46 +302,93 @@ impl RawTransactionsResponseBuilder {
self
}
+ /// Builds the raw transaction json result
+ pub fn build_result(&mut self) -> serde_json::Value {
+ serde_json::json!(
+ self.serial_ids
+ .iter()
+ .map(|serial_id| serde_json::json!({
+ "common_data": {
+ "L1": {
+ "sender": "0xcca8009f5e09f8c5db63cb0031052f9cb635af62",
+ "serialId": serial_id,
+ "deadlineBlock": 0,
+ "layer2TipFee": "0x0",
+ "fullFee": "0x0",
+ "maxFeePerGas": "0x0",
+ "gasLimit": "0x989680",
+ "gasPerPubdataLimit": "0x320",
+ "opProcessingType": "Common",
+ "priorityQueueType": "Deque",
+ "ethHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "ethBlock": 16631249u64,
+ "canonicalTxHash": "0xaaf9514a005ba59e29b53e1dc84d234d909c5202b44c5179f9c67d8e3cad0636",
+ "toMint": "0x470de4df820000",
+ "refundRecipient": "0xcca8009f5e09f8c5db63cb0031052f9cb635af62"
+ }
+ },
+ "execute": {
+ "contractAddress": "0xcca8009f5e09f8c5db63cb0031052f9cb635af62",
+ "calldata": "0x",
+ "value": "0x470de4df820000",
+ "factoryDeps": []
+ },
+ "received_timestamp_ms": 1676429272816u64,
+ "raw_bytes": null
+ }))
+ .collect_vec()
+ )
+ }
+
/// Builds the json response
pub fn build(&mut self) -> serde_json::Value {
serde_json::json!({
"jsonrpc": "2.0",
"id": 0,
- "result": self.serial_ids.iter().map(|serial_id| serde_json::json!({
- "common_data": {
- "L1": {
- "sender": "0xcca8009f5e09f8c5db63cb0031052f9cb635af62",
- "serialId": serial_id,
- "deadlineBlock": 0,
- "layer2TipFee": "0x0",
- "fullFee": "0x0",
- "maxFeePerGas": "0x0",
- "gasLimit": "0x989680",
- "gasPerPubdataLimit": "0x320",
- "opProcessingType": "Common",
- "priorityQueueType": "Deque",
- "ethHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "ethBlock": 16631249u64,
- "canonicalTxHash": "0xaaf9514a005ba59e29b53e1dc84d234d909c5202b44c5179f9c67d8e3cad0636",
- "toMint": "0x470de4df820000",
- "refundRecipient": "0xcca8009f5e09f8c5db63cb0031052f9cb635af62"
- }
- },
- "execute": {
- "contractAddress": "0xcca8009f5e09f8c5db63cb0031052f9cb635af62",
- "calldata": "0x",
- "value": "0x470de4df820000",
- "factoryDeps": []
- },
- "received_timestamp_ms": 1676429272816u64,
- "raw_bytes": null
- })).collect_vec(),
+ "result": self.build_result(),
})
}
}
+/// Applies a transaction with a given hash to the node and returns the block hash.
+pub fn apply_tx(node: &InMemoryNode, tx_hash: H256) -> H256 {
+ let current_batch = node
+ .get_inner()
+ .read()
+ .map(|reader| reader.current_batch)
+ .expect("failed getting current batch number");
+ let produced_block_hash = compute_hash(current_batch, tx_hash);
+
+ let private_key = H256::random();
+ let from_account = PackedEthSignature::address_from_private_key(&private_key)
+ .expect("failed generating address");
+ node.set_rich_account(from_account);
+ let mut tx = L2Tx::new_signed(
+ Address::random(),
+ vec![],
+ Nonce(0),
+ Fee {
+ gas_limit: U256::from(1_000_000),
+ max_fee_per_gas: U256::from(250_000_000),
+ max_priority_fee_per_gas: U256::from(250_000_000),
+ gas_per_pubdata_limit: U256::from(20000),
+ },
+ U256::from(1),
+ L2ChainId(260),
+ &private_key,
+ None,
+ Default::default(),
+ )
+ .unwrap();
+ tx.set_input(vec![], tx_hash);
+ node.apply_txs(vec![tx.into()]).expect("failed applying tx");
+
+ produced_block_hash
+}
+
mod test {
use super::*;
+ use crate::http_fork_source::HttpForkSource;
#[test]
fn test_block_response_builder_set_hash() {
@@ -344,4 +420,77 @@ mod test {
assert_eq!("0xff", actual_value);
}
+
+ #[test]
+ fn test_transaction_response_builder_set_hash() {
+ let builder = TransactionResponseBuilder::new()
+ .set_hash(H256::repeat_byte(0x01))
+ .build();
+
+ let actual_value = builder
+ .as_object()
+ .and_then(|o| o.get("result").unwrap().as_object())
+ .and_then(|o| o.get("hash").unwrap().as_str())
+ .expect("failed retrieving value");
+
+ assert_eq!(
+ "0x0101010101010101010101010101010101010101010101010101010101010101",
+ actual_value
+ );
+ }
+
+ #[test]
+ fn test_raw_transactions_response_builder_no_items() {
+ let builder = RawTransactionsResponseBuilder::new().build();
+
+ let actual_len = builder
+ .as_object()
+ .and_then(|o| o.get("result").unwrap().as_array())
+ .map(|o| o.len())
+ .expect("failed retrieving value");
+
+ assert_eq!(0, actual_len);
+ }
+
+ #[test]
+ fn test_raw_transactions_response_builder_added_items() {
+ let builder = RawTransactionsResponseBuilder::new()
+ .add(10)
+ .add(11)
+ .build();
+
+ let actual_serial_ids = builder
+ .as_object()
+ .and_then(|o| o.get("result").unwrap().as_array())
+ .map(|o| {
+ o.into_iter()
+ .map(|o| o.get("common_data").unwrap().as_object().unwrap())
+ .map(|o| o.get("L1").unwrap().as_object().unwrap())
+ .map(|entry| entry.get("serialId").unwrap().as_u64().unwrap())
+ .collect_vec()
+ })
+ .expect("failed retrieving value");
+
+ assert_eq!(vec![10, 11], actual_serial_ids);
+ }
+
+ #[tokio::test]
+ async fn test_apply_tx() {
+ let node = InMemoryNode::::default();
+ let actual_block_hash = apply_tx(&node, H256::repeat_byte(0x01));
+
+ assert_eq!(
+ H256::from_str("0x89c0aa770eba1f187235bdad80de9c01fe81bca415d442ca892f087da56fa109")
+ .unwrap(),
+ actual_block_hash,
+ );
+
+ assert!(
+ node.get_inner()
+ .read()
+ .map(|inner| inner.blocks.contains_key(&actual_block_hash))
+ .unwrap(),
+ "block was not produced"
+ );
+ }
}
diff --git a/test_endpoints.http b/test_endpoints.http
index e106f23b..dec74b33 100644
--- a/test_endpoints.http
+++ b/test_endpoints.http
@@ -365,6 +365,51 @@ content-type: application/json
POST http://localhost:8011
content-type: application/json
+{
+ "jsonrpc": "2.0",
+ "id": "2",
+ "method": "eth_getBlockTransactionCountByHash",
+ "params": ["0x371c9871792d89fea30e6b947ca45e9798e4f577dcdddb79269a93450f52b459"]
+}
+
+
+###
+POST http://localhost:8011
+content-type: application/json
+
+{
+ "jsonrpc": "2.0",
+ "id": "2",
+ "method": "eth_getBlockTransactionCountByNumber",
+ "params": ["0xc9d5bb"]
+}
+
+###
+POST http://localhost:8011
+content-type: application/json
+
+{
+ "jsonrpc": "2.0",
+ "id": "2",
+ "method": "eth_getBlockTransactionCountByNumber",
+ "params": ["latest"]
+}
+
+###
+POST http://localhost:8011
+content-type: application/json
+
+{
+ "jsonrpc": "2.0",
+ "id": "2",
+ "method": "eth_getBlockTransactionCountByNumber",
+ "params": ["earliest"]
+}
+
+###
+POST http://localhost:8011
+content-type: application/json
+
{
"jsonrpc": "2.0",
"id": "2",