diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index 09693583..fec03c5e 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 824a7bad..8183497f 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 03c8ab73..7548558b 100644 --- a/test_endpoints.http +++ b/test_endpoints.http @@ -354,6 +354,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",