diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index 62397aa2..024f30d0 100644 --- a/SUPPORTED_APIS.md +++ b/SUPPORTED_APIS.md @@ -121,7 +121,7 @@ The `status` options are: | `ZKS` | `zks_getL2ToL1LogProof` | `NOT IMPLEMENTED` | Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log | | `ZKS` | `zks_getL2ToL1MsgProof` | `NOT IMPLEMENTED` | Given a block, a sender, a message, and an optional message log index in the block containing the L1->L2 message, it returns the proof for the message sent via the L1Messenger system contract | | `ZKS` | `zks_getMainContract` | `NOT IMPLEMENTED` | Returns the address of the zkSync Era contract | -| `ZKS` | `zks_getRawBlockTransactions` | `NOT IMPLEMENTED` | Returns data of transactions in a block | +| [`ZKS`](#zks-namespace) | [`zks_getRawBlockTransactions`](#zks_getrawblocktransactions) | `SUPPORTED` | Returns data of transactions in a block | | `ZKS` | `zks_getTestnetPaymaster` | `NOT IMPLEMENTED` | Returns the address of the testnet paymaster | | [`ZKS`](#zks-namespace) | [`zks_getTokenPrice`](#zks_getTokenPrice) | `SUPPORTED` | Gets the USD price of a token
_(`ETH` is hard-coded to `1_500`, while some others are `1`)_ | | [`ZKS`](#zks-namespace) | [`zks_getTransactionDetails`](#zks_gettransactiondetails) | `SUPPORTED` | Returns data from a specific transaction given by the transaction hash | @@ -1791,4 +1791,27 @@ curl --request POST \ "id": "2", "method": "zks_getBridgeContracts" }' -``` \ No newline at end of file +``` + +### `zks_getRawBlockTransactions` + +[source](src/zks.rs) + +Returns data of transactions in a 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_getRawBlockTransactions", "params": [ 140599 ]}' +``` diff --git a/e2e-tests/test/zks-apis.test.ts b/e2e-tests/test/zks-apis.test.ts index eef4adcb..35770486 100644 --- a/e2e-tests/test/zks-apis.test.ts +++ b/e2e-tests/test/zks-apis.test.ts @@ -136,3 +136,19 @@ describe("zks_getBytecodeByHash", function () { expect(ethers.utils.hexlify(bytecode)).to.equal(artifact.deployedBytecode); }); }); + +describe("zks_getRawBlockTransactions", function () { + it("Should return transactions 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"]); + const receipt = await greeter.setGreeting("Luke Skywalker"); + + const latestBlock = await provider.getBlock("latest"); + const txns = await provider.send("zks_getRawBlockTransactions", [latestBlock.number - 1]); + + expect(txns.length).to.equal(1); + expect(txns[0]["execute"]["calldata"]).to.equal(receipt.data); + }); +}); diff --git a/src/zks.rs b/src/zks.rs index 9fc2bd4f..96606432 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -4,16 +4,17 @@ use bigdecimal::BigDecimal; use futures::FutureExt; use zksync_basic_types::{Address, L1BatchNumber, MiniblockNumber, U256}; use zksync_core::api_server::web3::backend_jsonrpc::{ - error::into_jsrpc_error, namespaces::zks::ZksNamespaceT, + error::{internal_error, into_jsrpc_error}, + namespaces::zks::ZksNamespaceT, }; use zksync_state::ReadStorage; use zksync_types::{ api::{ BlockDetails, BlockDetailsBase, BlockStatus, BridgeAddresses, ProtocolVersion, - TransactionDetails, TransactionStatus, + TransactionDetails, TransactionStatus, TransactionVariant, }, fee::Fee, - ProtocolVersionId, + ExecuteTransactionCommon, ProtocolVersionId, Transaction, }; use zksync_web3_decl::{ error::Web3Error, @@ -69,11 +70,74 @@ impl ZksNamespaceT } } + /// Returns data of transactions in a block. + /// + /// # Arguments + /// + /// * `block` - Block number + /// + /// # Returns + /// + /// A `BoxFuture` containing a `Result` with a `Vec` of `Transaction`s representing the transactions in the block. fn get_raw_block_transactions( &self, - _block_number: MiniblockNumber, + block_number: MiniblockNumber, ) -> jsonrpc_core::BoxFuture>> { - not_implemented("zks_getRawBlockTransactions") + let inner = self.node.clone(); + Box::pin(async move { + let reader = inner + .read() + .map_err(|_err| into_jsrpc_error(Web3Error::InternalError))?; + + let maybe_transactions = reader + .block_hashes + .get(&(block_number.0 as u64)) + .and_then(|hash| reader.blocks.get(hash)) + .map(|block| { + block + .transactions + .iter() + .map(|tx| match tx { + TransactionVariant::Full(tx) => &tx.hash, + TransactionVariant::Hash(hash) => hash, + }) + .flat_map(|tx_hash| { + reader.tx_results.get(tx_hash).map( + |TransactionResult { info, .. }| Transaction { + common_data: ExecuteTransactionCommon::L2( + info.tx.common_data.clone(), + ), + execute: info.tx.execute.clone(), + received_timestamp_ms: info.tx.received_timestamp_ms, + raw_bytes: info.tx.raw_bytes.clone(), + }, + ) + }) + .collect() + }); + + let transactions = match maybe_transactions { + Some(txns) => Ok(txns), + None => { + let fork_storage_read = reader + .fork_storage + .inner + .read() + .expect("failed reading fork storage"); + + match fork_storage_read.fork.as_ref() { + Some(fork) => fork + .fork_source + .get_raw_block_transactions(block_number) + .map_err(|e| internal_error("get_raw_block_transactions", e)), + None => Ok(vec![]), + } + } + } + .map_err(into_jsrpc_error)?; + + Ok(transactions) + }) } fn estimate_gas_l1_to_l2( @@ -442,7 +506,7 @@ mod tests { use super::*; use zksync_basic_types::{Address, H160, H256}; - use zksync_types::api::{Block, TransactionReceipt, TransactionVariant}; + use zksync_types::api::{self, Block, TransactionReceipt, TransactionVariant}; use zksync_types::transaction_request::CallRequest; #[tokio::test] @@ -865,4 +929,129 @@ mod tests { // Assert assert_eq!(input_bytecode, actual); } + + #[tokio::test] + async fn test_get_raw_block_transactions_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 mut block = Block::::default(); + let txn = api::Transaction::default(); + writer.tx_results.insert( + txn.hash, + TransactionResult { + info: testing::default_tx_execution_info(), + receipt: TransactionReceipt { + logs: vec![], + gas_used: Some(U256::from(10_000)), + effective_gas_price: Some(U256::from(1_000_000_000)), + ..Default::default() + }, + debug: testing::default_tx_debug_info(), + }, + ); + block.transactions.push(TransactionVariant::Full(txn)); + writer.blocks.insert(H256::repeat_byte(0x1), block); + writer.block_hashes.insert(0, H256::repeat_byte(0x1)); + } + + // Act + let txns = namespace + .get_raw_block_transactions(MiniblockNumber(0)) + .await + .expect("get transaction details"); + + // Assert + assert_eq!(txns.len(), 1); + } + + #[tokio::test] + async fn test_get_raw_block_transactions_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_getRawBlockTransactions", + "params": [miniblock.0] + }), + serde_json::json!({ + "jsonrpc": "2.0", + "result": [ + { + "common_data": { + "L2": { + "nonce": 86, + "fee": { + "gas_limit": "0xcc626", + "max_fee_per_gas": "0x141dd760", + "max_priority_fee_per_gas": "0x0", + "gas_per_pubdata_limit": "0x4e20" + }, + "initiatorAddress": "0x840bd73f903ba7dbb501be8326fe521dadcae1a5", + "signature": [ + 135, + 163, + 2, + 78, + 118, + 14, + 209 + ], + "transactionType": "EIP1559Transaction", + "input": { + "hash": "0xc1f625f55d186ad0b439054adfe3317ae703c5f588f4fa1896215e8810a141e0", + "data": [ + 2, + 249, + 1, + 110, + 130 + ] + }, + "paymasterParams": { + "paymaster": "0x0000000000000000000000000000000000000000", + "paymasterInput": [] + } + } + }, + "execute": { + "contractAddress": "0xbe7d1fd1f6748bbdefc4fbacafbb11c6fc506d1d", + "calldata": "0x38ed173900000000000000000000000000000000000000000000000000000000002c34cc00000000000000000000000000000000000000000000000000000000002c9a2500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000840bd73f903ba7dbb501be8326fe521dadcae1a500000000000000000000000000000000000000000000000000000000652c5d1900000000000000000000000000000000000000000000000000000000000000020000000000000000000000008e86e46278518efc1c5ced245cba2c7e3ef115570000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4", + "value": "0x0", + "factoryDeps": null + }, + "received_timestamp_ms": 1697405097873u64, + "raw_bytes": "0x02f9016e820144568084141dd760830cc62694be7d1fd1f6748bbdefc4fbacafbb11c6fc506d1d80b9010438ed173900000000000000000000000000000000000000000000000000000000002c34cc00000000000000000000000000000000000000000000000000000000002c9a2500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000840bd73f903ba7dbb501be8326fe521dadcae1a500000000000000000000000000000000000000000000000000000000652c5d1900000000000000000000000000000000000000000000000000000000000000020000000000000000000000008e86e46278518efc1c5ced245cba2c7e3ef115570000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4c080a087a3024e760ed14134ef541608bf308e083c899a89dba3c02bf3040f07c8b91b9fc3a7eeb6b3b8b36bb03ea4352415e7815dda4954f4898d255bd7660736285e" + } + ], + "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 txns = namespace + .get_raw_block_transactions(miniblock) + .await + .expect("get transaction details"); + assert_eq!(txns.len(), 1); + } } diff --git a/test_endpoints.http b/test_endpoints.http index 90ef7e79..d7554850 100644 --- a/test_endpoints.http +++ b/test_endpoints.http @@ -718,6 +718,17 @@ content-type: application/json POST http://localhost:8011 content-type: application/json +{ + "jsonrpc": "2.0", + "id": "1", + "method": "zks_getRawBlockTransactions", + "params": [16474138] +} + +### +POST http://localhost:8011 +content-type: application/json + { "jsonrpc": "2.0", "id": "1",