From 176e7d9df08450c97eb7c0907b75d1f347fa5bb6 Mon Sep 17 00:00:00 2001 From: grw-ms Date: Sun, 15 Oct 2023 14:44:19 -0700 Subject: [PATCH 1/4] feat: impl zks_getBlockDetails --- SUPPORTED_APIS.md | 25 ++++- e2e-tests/test/zks-apis.test.ts | 15 +++ src/fork.rs | 6 +- src/http_fork_source.rs | 63 ++++++++++++ src/zks.rs | 170 +++++++++++++++++++++++++++++++- test_endpoints.http | 11 +++ 6 files changed, 283 insertions(+), 7 deletions(-) diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index ebec2e49..033d898e 100644 --- a/SUPPORTED_APIS.md +++ b/SUPPORTED_APIS.md @@ -112,7 +112,7 @@ The `status` options are: | [`ZKS`](#zks-namespace) | [`zks_estimateFee`](#zks_estimateFee) | `SUPPORTED` | Gets the Fee estimation data for a given Request | | `ZKS` | `zks_estimateGasL1ToL2` | `NOT IMPLEMENTED` | Estimate of the gas required for a L1 to L2 transaction | | `ZKS` | `zks_getAllAccountBalances` | `NOT IMPLEMENTED` | Returns all balances for confirmed tokens given by an account address | -| `ZKS` | `zks_getBlockDetails` | `NOT IMPLEMENTED` | Returns additional zkSync-specific information about the L2 block | +| [`ZKS`](#zks-namespace) | [`zks_getBlockDetails`](#zks_getblockdetails) | `SUPPORTED` | Returns additional zkSync-specific information about the L2 block | | `ZKS` | `zks_getBridgeContracts` | `NOT IMPLEMENTED` | Returns L1/L2 addresses of default bridges | | `ZKS` | `zks_getBytecodeByHash` | `NOT IMPLEMENTED` | Returns bytecode of a transaction given by its hash | | `ZKS` | `zks_getConfirmedTokens` | `NOT IMPLEMENTED` | Returns [address, symbol, name, and decimal] information of all tokens within a range of ids given by parameters `from` and `limit` | @@ -1742,3 +1742,26 @@ curl --request POST \ --header 'content-type: application/json' \ --data '{"jsonrpc": "2.0","id": "1","method": "zks_getTransactionDetails","params": ["0xa5d62a85561295ed58f8daad4e9442691e6da4301a859f364d28a02917d6e04d"]}' ``` + +### `zks_getBlockDetails` + +[source](src/zks.rs) + +Returns additional zkSync-specific information about the L2 block. + +#### Arguments + ++ `block: u32` - The number of the block + +#### Status + +`SUPPORTED` + +#### Example + +```bash +curl --request POST \ + --url http://localhost:8011/ \ + --header 'content-type: application/json' \ + --data '{"jsonrpc": "2.0", "id": 1, "method": "zks_getBlockDetails", "params": [ 140599 ]}' +``` diff --git a/e2e-tests/test/zks-apis.test.ts b/e2e-tests/test/zks-apis.test.ts index 9e5b6a8f..0f169cce 100644 --- a/e2e-tests/test/zks-apis.test.ts +++ b/e2e-tests/test/zks-apis.test.ts @@ -74,3 +74,18 @@ describe("zks_getTransactionDetails", function () { expect(details["initiatorAddress"].toLowerCase()).to.equal(wallet.address.toLowerCase()); }); }); + +describe("zks_getBlockDetails", function () { + it("Should return block details for locally-produced blocks", async function () { + const wallet = new Wallet(RichAccounts[0].PrivateKey); + const deployer = new Deployer(hre, wallet); + + const greeter = await deployContract(deployer, "Greeter", ["Hi"]); + await greeter.setGreeting("Luke Skywalker"); + + const latestBlock = await provider.getBlock("latest"); + const details = await provider.send("zks_getBlockDetails", [latestBlock.number]); + + expect(details['timestamp']).to.equal(latestBlock.timestamp); + }); +}); diff --git a/src/fork.rs b/src/fork.rs index bb085155..d8f73780 100644 --- a/src/fork.rs +++ b/src/fork.rs @@ -15,7 +15,8 @@ use zksync_basic_types::{Address, L1BatchNumber, L2ChainId, MiniblockNumber, H25 use zksync_types::{ api::{ - Block, BlockIdVariant, BlockNumber, Transaction, TransactionDetails, TransactionVariant, + Block, BlockDetails, BlockIdVariant, BlockNumber, Transaction, TransactionDetails, + TransactionVariant, }, l2::L2Tx, ProtocolVersionId, StorageKey, @@ -231,6 +232,9 @@ pub trait ForkSource { full_transactions: bool, ) -> eyre::Result>>; + /// Returns the block details for a given miniblock number. + fn get_block_details(&self, miniblock: MiniblockNumber) -> eyre::Result>; + /// Returns the transaction count for a given block hash. fn get_block_transaction_count_by_hash(&self, block_hash: H256) -> eyre::Result>; diff --git a/src/http_fork_source.rs b/src/http_fork_source.rs index 203f825d..ecf640de 100644 --- a/src/http_fork_source.rs +++ b/src/http_fork_source.rs @@ -264,6 +264,16 @@ impl ForkSource for HttpForkSource { }) .wrap_err("fork http client failed") } + + /// Returns details of a block, given miniblock number + fn get_block_details( + &self, + miniblock: zksync_basic_types::MiniblockNumber, + ) -> eyre::Result> { + let client = self.create_client(); + block_on(async move { client.get_block_details(miniblock).await }) + .wrap_err("fork http client failed") + } } #[cfg(test)] @@ -553,4 +563,57 @@ mod tests { Address::from_str("0x63ab285cd87a189f345fed7dd4e33780393e01f0").unwrap() ); } + + #[test] + fn test_get_block_details() { + let miniblock = MiniblockNumber::from(16474138); + let mock_server = testing::MockServer::run(); + mock_server.expect( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 0, + "method": "zks_getBlockDetails", + "params": [ + miniblock.0, + ], + }), + serde_json::json!({ + "jsonrpc": "2.0", + "result": { + "number": 16474138, + "l1BatchNumber": 270435, + "timestamp": 1697405098, + "l1TxCount": 0, + "l2TxCount": 1, + "rootHash": "0xd9e60f9a684fd7fc16e87ae923341a6e4af24f286e76612efdfc2d55f3f4d064", + "status": "sealed", + "commitTxHash": null, + "committedAt": null, + "proveTxHash": null, + "provenAt": null, + "executeTxHash": null, + "executedAt": null, + "l1GasPrice": 6156252068u64, + "l2FairGasPrice": 250000000u64, + "baseSystemContractsHashes": { + "bootloader": "0x0100089b8a2f2e6a20ba28f02c9e0ed0c13d702932364561a0ea61621f65f0a8", + "default_aa": "0x0100067d16a5485875b4249040bf421f53e869337fe118ec747cf40a4c777e5f" + }, + "operatorAddress": "0xa9232040bf0e0aea2578a5b2243f2916dbfc0a69", + "protocolVersion": "Version15" + }, + "id": 0 + }), + ); + + let fork_source = HttpForkSource::new(mock_server.url(), CacheConfig::Memory); + let block_details = fork_source + .get_block_details(miniblock) + .expect("failed fetching transaction") + .expect("no transaction"); + assert_eq!( + block_details.operator_address, + Address::from_str("0xa9232040bf0e0aea2578a5b2243f2916dbfc0a69").unwrap() + ); + } } diff --git a/src/zks.rs b/src/zks.rs index addaf952..b3ea48f6 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -2,13 +2,17 @@ use std::sync::{Arc, RwLock}; use bigdecimal::BigDecimal; use futures::FutureExt; -use zksync_basic_types::{MiniblockNumber, U256}; +use zksync_basic_types::{Address, L1BatchNumber, MiniblockNumber, U256}; use zksync_core::api_server::web3::backend_jsonrpc::{ error::into_jsrpc_error, namespaces::zks::ZksNamespaceT, }; use zksync_types::{ - api::{BridgeAddresses, ProtocolVersion, TransactionDetails, TransactionStatus}, + api::{ + BlockDetails, BlockDetailsBase, BlockStatus, BridgeAddresses, ProtocolVersion, + TransactionDetails, TransactionStatus, + }, fee::Fee, + ProtocolVersionId, }; use zksync_web3_decl::{ error::Web3Error, @@ -180,12 +184,75 @@ impl ZksNamespaceT not_implemented("zks_L1BatchNumber") } + /// Get block details. + /// + /// # Arguments + /// + /// * `blockNumber` - `u32` miniblock number + /// + /// # Returns + /// + /// A `BoxFuture` containing a `Result` with an `Option` representing details of the block (if found). fn get_block_details( &self, - _block_number: zksync_basic_types::MiniblockNumber, + block_number: zksync_basic_types::MiniblockNumber, ) -> jsonrpc_core::BoxFuture>> { - not_implemented("zks_getBlockDetails") + let inner = self.node.clone(); + Box::pin(async move { + let reader = inner + .read() + .map_err(|_err| into_jsrpc_error(Web3Error::InternalError))?; + + let maybe_block = reader + .block_hashes + .get(&(block_number.0 as u64)) + .and_then(|hash| reader.blocks.get(hash)) + .map(|block| BlockDetails { + number: MiniblockNumber(block.number.as_u32()), + l1_batch_number: L1BatchNumber( + block.l1_batch_number.unwrap_or_default().as_u32(), + ), + base: BlockDetailsBase { + timestamp: block.timestamp.as_u64(), + l1_tx_count: 1, + l2_tx_count: 1, + root_hash: None, + status: BlockStatus::Verified, + commit_tx_hash: None, + committed_at: None, + prove_tx_hash: None, + proven_at: None, + execute_tx_hash: None, + executed_at: None, + l1_gas_price: 0, + l2_fair_gas_price: 0, + base_system_contracts_hashes: reader + .system_contracts + .baseline_contracts + .hashes(), + }, + operator_address: Address::zero(), + protocol_version: Some(ProtocolVersionId::latest()), + }) + .or_else(|| { + reader + .fork_storage + .inner + .read() + .expect("failed reading fork storage") + .fork + .as_ref() + .and_then(|fork| { + fork.fork_source + .get_block_details(block_number) + .ok() + .flatten() + }) + }); + + Ok(maybe_block) + }) } fn get_miniblock_range( @@ -320,7 +387,7 @@ mod tests { use super::*; use zksync_basic_types::{Address, H256}; - use zksync_types::api::TransactionReceipt; + use zksync_types::api::{Block, TransactionReceipt, TransactionVariant}; use zksync_types::transaction_request::CallRequest; #[tokio::test] @@ -503,4 +570,97 @@ mod tests { assert!(matches!(result.status, TransactionStatus::Included)); assert_eq!(result.fee, U256::from(127_720_500_000_000u64)); } + + #[tokio::test] + async fn test_get_block_details_local() { + // Arrange + let node = InMemoryNode::::default(); + let namespace = ZkMockNamespaceImpl::new(node.get_inner()); + let inner = node.get_inner(); + { + let mut writer = inner.write().unwrap(); + let block = Block::::default(); + writer.blocks.insert(H256::repeat_byte(0x1), block); + writer.block_hashes.insert(0, H256::repeat_byte(0x1)); + } + // Act + let result = namespace + .get_block_details(MiniblockNumber(0)) + .await + .expect("get block details") + .expect("block details"); + + // Assert + assert!(matches!(result.number, MiniblockNumber(0))); + assert_eq!(result.l1_batch_number, L1BatchNumber(0)); + assert_eq!(result.base.timestamp, 0); + } + + #[tokio::test] + async fn test_get_block_details_fork() { + let mock_server = MockServer::run_with_config(ForkBlockConfig { + number: 10, + transaction_count: 0, + hash: H256::repeat_byte(0xab), + }); + let miniblock = MiniblockNumber::from(16474138); + mock_server.expect( + serde_json::json!({ + "jsonrpc": "2.0", + "id": 0, + "method": "zks_getBlockDetails", + "params": [ + miniblock.0, + ], + }), + serde_json::json!({ + "jsonrpc": "2.0", + "result": { + "number": 16474138, + "l1BatchNumber": 270435, + "timestamp": 1697405098, + "l1TxCount": 0, + "l2TxCount": 1, + "rootHash": "0xd9e60f9a684fd7fc16e87ae923341a6e4af24f286e76612efdfc2d55f3f4d064", + "status": "sealed", + "commitTxHash": null, + "committedAt": null, + "proveTxHash": null, + "provenAt": null, + "executeTxHash": null, + "executedAt": null, + "l1GasPrice": 6156252068u64, + "l2FairGasPrice": 250000000u64, + "baseSystemContractsHashes": { + "bootloader": "0x0100089b8a2f2e6a20ba28f02c9e0ed0c13d702932364561a0ea61621f65f0a8", + "default_aa": "0x0100067d16a5485875b4249040bf421f53e869337fe118ec747cf40a4c777e5f" + }, + "operatorAddress": "0xa9232040bf0e0aea2578a5b2243f2916dbfc0a69", + "protocolVersion": "Version15" + }, + "id": 0 + }), + ); + + 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 namespace = ZkMockNamespaceImpl::new(node.get_inner()); + let result = namespace + .get_block_details(miniblock) + .await + .expect("get block details") + .expect("block details"); + + assert!(matches!(result.number, MiniblockNumber(16474138))); + assert_eq!(result.l1_batch_number, L1BatchNumber(270435)); + assert_eq!(result.base.timestamp, 1697405098); + } } diff --git a/test_endpoints.http b/test_endpoints.http index 9977f07d..96d372f7 100644 --- a/test_endpoints.http +++ b/test_endpoints.http @@ -697,6 +697,17 @@ content-type: application/json POST http://localhost:8011 content-type: application/json +{ + "jsonrpc": "2.0", + "id": "1", + "method": "zks_getBlockDetails", + "params": [16474138] +} + +### +POST http://localhost:8011 +content-type: application/json + { "jsonrpc": "2.0", "id": "1", From a4c2e8d8d68c7f0276f33347be9a7fafbedf892c Mon Sep 17 00:00:00 2001 From: grw-ms Date: Mon, 16 Oct 2023 04:06:12 -0700 Subject: [PATCH 2/4] fix: review comments --- src/zks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zks.rs b/src/zks.rs index b3ea48f6..740097a7 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -216,8 +216,8 @@ impl ZksNamespaceT base: BlockDetailsBase { timestamp: block.timestamp.as_u64(), l1_tx_count: 1, - l2_tx_count: 1, - root_hash: None, + l2_tx_count: block.transactions.len(), + root_hash: Some(block.hash), status: BlockStatus::Verified, commit_tx_hash: None, committed_at: None, From 6e2d61b1b4ccbf21f6db9dee96c00b6d0cf9bc9b Mon Sep 17 00:00:00 2001 From: grw-ms Date: Tue, 17 Oct 2023 02:54:45 -0700 Subject: [PATCH 3/4] chore: lint --- e2e-tests/test/zks-apis.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/test/zks-apis.test.ts b/e2e-tests/test/zks-apis.test.ts index 0f169cce..e5859a1f 100644 --- a/e2e-tests/test/zks-apis.test.ts +++ b/e2e-tests/test/zks-apis.test.ts @@ -86,6 +86,6 @@ describe("zks_getBlockDetails", function () { const latestBlock = await provider.getBlock("latest"); const details = await provider.send("zks_getBlockDetails", [latestBlock.number]); - expect(details['timestamp']).to.equal(latestBlock.timestamp); + expect(details["timestamp"]).to.equal(latestBlock.timestamp); }); }); From e84bdfd8ae570c40446e79569b0241a44595a4c0 Mon Sep 17 00:00:00 2001 From: grw-ms Date: Tue, 17 Oct 2023 02:55:25 -0700 Subject: [PATCH 4/4] fix: use L2_GAS_PRICE for l2_fair_gas_price --- src/zks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zks.rs b/src/zks.rs index 740097a7..06c9e2a2 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -21,7 +21,7 @@ use zksync_web3_decl::{ use crate::{ fork::ForkSource, - node::{InMemoryNodeInner, TransactionResult}, + node::{InMemoryNodeInner, TransactionResult, L2_GAS_PRICE}, utils::{not_implemented, utc_datetime_from_epoch_ms, IntoBoxedFuture}, }; use colored::Colorize; @@ -226,7 +226,7 @@ impl ZksNamespaceT execute_tx_hash: None, executed_at: None, l1_gas_price: 0, - l2_fair_gas_price: 0, + l2_fair_gas_price: L2_GAS_PRICE, base_system_contracts_hashes: reader .system_contracts .baseline_contracts