From 50422b897d2b0fdbb82f1c4cdb97c1a39ace02c7 Mon Sep 17 00:00:00 2001 From: Ivan Schasny <31857042+ischasny@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:38:52 +0100 Subject: [PATCH] feat: add block timestamp to `eth_getLogs` (#2374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Add a new field `blockTimestamp` to the `eth_getLogs` endpoint. ## Why ❔ That'd allow network indexers to avoid sending a second requests just to get timestamp for each block. More [info](https://ethereum-magicians.org/t/proposal-for-adding-blocktimestamp-to-logs-object-returned-by-eth-getlogs-and-related-requests/11183) --- core/lib/basic_types/src/web3/mod.rs | 3 +++ ...ed811faffcc108d04b59fdec5a0ab9d13fa3.json} | 12 ++++++--- ...3acd2066a5e238088b39b982b10770f51479.json} | 10 +++++-- ...ad74e1bab808c744fa14bf24332b39120767.json} | 12 ++++++--- core/lib/dal/src/events_dal.rs | 3 ++- core/lib/dal/src/events_web3_dal.rs | 6 +++-- core/lib/dal/src/models/storage_event.rs | 2 ++ .../lib/dal/src/models/storage_transaction.rs | 1 + core/lib/dal/src/transactions_web3_dal.rs | 19 +++++++++----- core/lib/types/src/api/mod.rs | 3 +++ core/lib/types/src/event/mod.rs | 1 + core/lib/types/src/protocol_upgrade.rs | 1 + .../node/consistency_checker/src/tests/mod.rs | 1 + core/node/eth_watch/src/tests.rs | 2 ++ .../ts-integration/tests/api/web3.test.ts | 26 +++++++++++++++++++ 15 files changed, 84 insertions(+), 18 deletions(-) rename core/lib/dal/.sqlx/{query-3ba9bc85e3e286aadef8aad27eb38fc90b18155e3435f58d9888fa50d92042f7.json => query-526a8e1c231e99faadd5dbbe9c49ed811faffcc108d04b59fdec5a0ab9d13fa3.json} (80%) rename core/lib/dal/.sqlx/{query-dcb51063c12341785e57f221e2d5ede2be9770b3799a9ab64fe9690b6eb0a48b.json => query-c3af06cc232adb93f16456da07733acd2066a5e238088b39b982b10770f51479.json} (84%) rename core/lib/dal/.sqlx/{query-d43d5c96ae92f52b12b320d5c6c43335d23bec1370e520186739d7075e9e3338.json => query-e1e8ab0cb11c6081d3525228eacbad74e1bab808c744fa14bf24332b39120767.json} (84%) diff --git a/core/lib/basic_types/src/web3/mod.rs b/core/lib/basic_types/src/web3/mod.rs index cfeeaa533b36..75bcfac62f24 100644 --- a/core/lib/basic_types/src/web3/mod.rs +++ b/core/lib/basic_types/src/web3/mod.rs @@ -327,6 +327,9 @@ pub struct Log { pub log_type: Option, /// Removed pub removed: Option, + /// L2 block timestamp + #[serde(rename = "blockTimestamp")] + pub block_timestamp: Option, } impl Log { diff --git a/core/lib/dal/.sqlx/query-3ba9bc85e3e286aadef8aad27eb38fc90b18155e3435f58d9888fa50d92042f7.json b/core/lib/dal/.sqlx/query-526a8e1c231e99faadd5dbbe9c49ed811faffcc108d04b59fdec5a0ab9d13fa3.json similarity index 80% rename from core/lib/dal/.sqlx/query-3ba9bc85e3e286aadef8aad27eb38fc90b18155e3435f58d9888fa50d92042f7.json rename to core/lib/dal/.sqlx/query-526a8e1c231e99faadd5dbbe9c49ed811faffcc108d04b59fdec5a0ab9d13fa3.json index 221e04e0c717..dbdec4ac5d65 100644 --- a/core/lib/dal/.sqlx/query-3ba9bc85e3e286aadef8aad27eb38fc90b18155e3435f58d9888fa50d92042f7.json +++ b/core/lib/dal/.sqlx/query-526a8e1c231e99faadd5dbbe9c49ed811faffcc108d04b59fdec5a0ab9d13fa3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n address,\n topic1,\n topic2,\n topic3,\n topic4,\n value,\n NULL::bytea AS \"block_hash\",\n NULL::BIGINT AS \"l1_batch_number?\",\n miniblock_number,\n tx_hash,\n tx_index_in_block,\n event_index_in_block,\n event_index_in_tx\n FROM\n events\n WHERE\n tx_hash = ANY ($1)\n ORDER BY\n miniblock_number ASC,\n event_index_in_block ASC\n ", + "query": "\n SELECT\n address,\n topic1,\n topic2,\n topic3,\n topic4,\n value,\n NULL::bytea AS \"block_hash\",\n NULL::BIGINT AS \"l1_batch_number?\",\n miniblock_number,\n tx_hash,\n tx_index_in_block,\n event_index_in_block,\n event_index_in_tx,\n NULL::BIGINT AS \"block_timestamp?\"\n FROM\n events\n WHERE\n tx_hash = ANY ($1)\n ORDER BY\n miniblock_number ASC,\n event_index_in_block ASC\n ", "describe": { "columns": [ { @@ -67,6 +67,11 @@ "ordinal": 12, "name": "event_index_in_tx", "type_info": "Int4" + }, + { + "ordinal": 13, + "name": "block_timestamp?", + "type_info": "Int8" } ], "parameters": { @@ -87,8 +92,9 @@ false, false, false, - false + false, + null ] }, - "hash": "3ba9bc85e3e286aadef8aad27eb38fc90b18155e3435f58d9888fa50d92042f7" + "hash": "526a8e1c231e99faadd5dbbe9c49ed811faffcc108d04b59fdec5a0ab9d13fa3" } diff --git a/core/lib/dal/.sqlx/query-dcb51063c12341785e57f221e2d5ede2be9770b3799a9ab64fe9690b6eb0a48b.json b/core/lib/dal/.sqlx/query-c3af06cc232adb93f16456da07733acd2066a5e238088b39b982b10770f51479.json similarity index 84% rename from core/lib/dal/.sqlx/query-dcb51063c12341785e57f221e2d5ede2be9770b3799a9ab64fe9690b6eb0a48b.json rename to core/lib/dal/.sqlx/query-c3af06cc232adb93f16456da07733acd2066a5e238088b39b982b10770f51479.json index 0ee5b247c330..1c15bde02fdf 100644 --- a/core/lib/dal/.sqlx/query-dcb51063c12341785e57f221e2d5ede2be9770b3799a9ab64fe9690b6eb0a48b.json +++ b/core/lib/dal/.sqlx/query-c3af06cc232adb93f16456da07733acd2066a5e238088b39b982b10770f51479.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n WITH\n events_select AS (\n SELECT\n address,\n topic1,\n topic2,\n topic3,\n topic4,\n value,\n miniblock_number,\n tx_hash,\n tx_index_in_block,\n event_index_in_block,\n event_index_in_tx\n FROM\n events\n WHERE\n miniblock_number > $1\n ORDER BY\n miniblock_number ASC,\n event_index_in_block ASC\n )\n SELECT\n miniblocks.hash AS \"block_hash?\",\n address AS \"address!\",\n topic1 AS \"topic1!\",\n topic2 AS \"topic2!\",\n topic3 AS \"topic3!\",\n topic4 AS \"topic4!\",\n value AS \"value!\",\n miniblock_number AS \"miniblock_number!\",\n miniblocks.l1_batch_number AS \"l1_batch_number?\",\n tx_hash AS \"tx_hash!\",\n tx_index_in_block AS \"tx_index_in_block!\",\n event_index_in_block AS \"event_index_in_block!\",\n event_index_in_tx AS \"event_index_in_tx!\"\n FROM\n events_select\n INNER JOIN miniblocks ON events_select.miniblock_number = miniblocks.number\n ORDER BY\n miniblock_number ASC,\n event_index_in_block ASC\n ", + "query": "\n WITH\n events_select AS (\n SELECT\n address,\n topic1,\n topic2,\n topic3,\n topic4,\n value,\n miniblock_number,\n tx_hash,\n tx_index_in_block,\n event_index_in_block,\n event_index_in_tx\n FROM\n events\n WHERE\n miniblock_number > $1\n ORDER BY\n miniblock_number ASC,\n event_index_in_block ASC\n )\n SELECT\n miniblocks.hash AS \"block_hash?\",\n address AS \"address!\",\n topic1 AS \"topic1!\",\n topic2 AS \"topic2!\",\n topic3 AS \"topic3!\",\n topic4 AS \"topic4!\",\n value AS \"value!\",\n miniblock_number AS \"miniblock_number!\",\n miniblocks.l1_batch_number AS \"l1_batch_number?\",\n tx_hash AS \"tx_hash!\",\n tx_index_in_block AS \"tx_index_in_block!\",\n event_index_in_block AS \"event_index_in_block!\",\n event_index_in_tx AS \"event_index_in_tx!\",\n miniblocks.timestamp AS \"block_timestamp\"\n FROM\n events_select\n INNER JOIN miniblocks ON events_select.miniblock_number = miniblocks.number\n ORDER BY\n miniblock_number ASC,\n event_index_in_block ASC\n ", "describe": { "columns": [ { @@ -67,6 +67,11 @@ "ordinal": 12, "name": "event_index_in_tx!", "type_info": "Int4" + }, + { + "ordinal": 13, + "name": "block_timestamp", + "type_info": "Int8" } ], "parameters": { @@ -87,8 +92,9 @@ false, false, false, + false, false ] }, - "hash": "dcb51063c12341785e57f221e2d5ede2be9770b3799a9ab64fe9690b6eb0a48b" + "hash": "c3af06cc232adb93f16456da07733acd2066a5e238088b39b982b10770f51479" } diff --git a/core/lib/dal/.sqlx/query-d43d5c96ae92f52b12b320d5c6c43335d23bec1370e520186739d7075e9e3338.json b/core/lib/dal/.sqlx/query-e1e8ab0cb11c6081d3525228eacbad74e1bab808c744fa14bf24332b39120767.json similarity index 84% rename from core/lib/dal/.sqlx/query-d43d5c96ae92f52b12b320d5c6c43335d23bec1370e520186739d7075e9e3338.json rename to core/lib/dal/.sqlx/query-e1e8ab0cb11c6081d3525228eacbad74e1bab808c744fa14bf24332b39120767.json index 93934a3a0bed..de9937ef7b95 100644 --- a/core/lib/dal/.sqlx/query-d43d5c96ae92f52b12b320d5c6c43335d23bec1370e520186739d7075e9e3338.json +++ b/core/lib/dal/.sqlx/query-e1e8ab0cb11c6081d3525228eacbad74e1bab808c744fa14bf24332b39120767.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n WITH\n events AS (\n SELECT DISTINCT\n ON (events.tx_hash) *\n FROM\n events\n WHERE\n events.address = $1\n AND events.topic1 = $2\n AND events.tx_hash = ANY ($3)\n ORDER BY\n events.tx_hash,\n events.event_index_in_tx DESC\n )\n SELECT\n transactions.hash AS tx_hash,\n transactions.index_in_block AS index_in_block,\n transactions.l1_batch_tx_index AS l1_batch_tx_index,\n transactions.miniblock_number AS \"block_number!\",\n transactions.error AS error,\n transactions.effective_gas_price AS effective_gas_price,\n transactions.initiator_address AS initiator_address,\n transactions.data -> 'to' AS \"transfer_to?\",\n transactions.data -> 'contractAddress' AS \"execute_contract_address?\",\n transactions.tx_format AS \"tx_format?\",\n transactions.refunded_gas AS refunded_gas,\n transactions.gas_limit AS gas_limit,\n miniblocks.hash AS \"block_hash\",\n miniblocks.l1_batch_number AS \"l1_batch_number?\",\n events.topic4 AS \"contract_address?\"\n FROM\n transactions\n JOIN miniblocks ON miniblocks.number = transactions.miniblock_number\n LEFT JOIN events ON events.tx_hash = transactions.hash\n WHERE\n transactions.hash = ANY ($3)\n AND transactions.data != '{}'::jsonb\n ", + "query": "\n WITH\n events AS (\n SELECT DISTINCT\n ON (events.tx_hash) *\n FROM\n events\n WHERE\n events.address = $1\n AND events.topic1 = $2\n AND events.tx_hash = ANY ($3)\n ORDER BY\n events.tx_hash,\n events.event_index_in_tx DESC\n )\n SELECT\n transactions.hash AS tx_hash,\n transactions.index_in_block AS index_in_block,\n transactions.l1_batch_tx_index AS l1_batch_tx_index,\n transactions.miniblock_number AS \"block_number!\",\n transactions.error AS error,\n transactions.effective_gas_price AS effective_gas_price,\n transactions.initiator_address AS initiator_address,\n transactions.data -> 'to' AS \"transfer_to?\",\n transactions.data -> 'contractAddress' AS \"execute_contract_address?\",\n transactions.tx_format AS \"tx_format?\",\n transactions.refunded_gas AS refunded_gas,\n transactions.gas_limit AS gas_limit,\n miniblocks.hash AS \"block_hash\",\n miniblocks.l1_batch_number AS \"l1_batch_number?\",\n events.topic4 AS \"contract_address?\",\n miniblocks.timestamp AS \"block_timestamp?\"\n FROM\n transactions\n JOIN miniblocks ON miniblocks.number = transactions.miniblock_number\n LEFT JOIN events ON events.tx_hash = transactions.hash\n WHERE\n transactions.hash = ANY ($3)\n AND transactions.data != '{}'::jsonb\n ", "describe": { "columns": [ { @@ -77,6 +77,11 @@ "ordinal": 14, "name": "contract_address?", "type_info": "Bytea" + }, + { + "ordinal": 15, + "name": "block_timestamp?", + "type_info": "Int8" } ], "parameters": { @@ -101,8 +106,9 @@ true, false, true, - true + true, + false ] }, - "hash": "d43d5c96ae92f52b12b320d5c6c43335d23bec1370e520186739d7075e9e3338" + "hash": "e1e8ab0cb11c6081d3525228eacbad74e1bab808c744fa14bf24332b39120767" } diff --git a/core/lib/dal/src/events_dal.rs b/core/lib/dal/src/events_dal.rs index 7bbffb23e320..c2b296fc085b 100644 --- a/core/lib/dal/src/events_dal.rs +++ b/core/lib/dal/src/events_dal.rs @@ -222,7 +222,8 @@ impl EventsDal<'_, '_> { tx_hash, tx_index_in_block, event_index_in_block, - event_index_in_tx + event_index_in_tx, + NULL::BIGINT AS "block_timestamp?" FROM events WHERE diff --git a/core/lib/dal/src/events_web3_dal.rs b/core/lib/dal/src/events_web3_dal.rs index 1a182f6052d5..fc21cc36460c 100644 --- a/core/lib/dal/src/events_web3_dal.rs +++ b/core/lib/dal/src/events_web3_dal.rs @@ -79,7 +79,8 @@ impl EventsWeb3Dal<'_, '_> { ORDER BY miniblock_number ASC, event_index_in_block ASC LIMIT ${} ) - SELECT miniblocks.hash as "block_hash", miniblocks.l1_batch_number as "l1_batch_number", events_select.* + SELECT miniblocks.hash as "block_hash", miniblocks.l1_batch_number as "l1_batch_number", + miniblocks.timestamp as block_timestamp, events_select.* FROM events_select INNER JOIN miniblocks ON events_select.miniblock_number = miniblocks.number ORDER BY miniblock_number ASC, event_index_in_block ASC @@ -222,7 +223,8 @@ impl EventsWeb3Dal<'_, '_> { tx_hash AS "tx_hash!", tx_index_in_block AS "tx_index_in_block!", event_index_in_block AS "event_index_in_block!", - event_index_in_tx AS "event_index_in_tx!" + event_index_in_tx AS "event_index_in_tx!", + miniblocks.timestamp AS "block_timestamp" FROM events_select INNER JOIN miniblocks ON events_select.miniblock_number = miniblocks.number diff --git a/core/lib/dal/src/models/storage_event.rs b/core/lib/dal/src/models/storage_event.rs index f741e2aa1202..415c39001ea0 100644 --- a/core/lib/dal/src/models/storage_event.rs +++ b/core/lib/dal/src/models/storage_event.rs @@ -20,6 +20,7 @@ pub struct StorageWeb3Log { pub tx_index_in_block: i32, pub event_index_in_block: i32, pub event_index_in_tx: i32, + pub block_timestamp: Option, } impl From for api::Log { @@ -47,6 +48,7 @@ impl From for api::Log { transaction_log_index: Some(U256::from(log.event_index_in_tx as u32)), log_type: None, removed: Some(false), + block_timestamp: log.block_timestamp, } } } diff --git a/core/lib/dal/src/models/storage_transaction.rs b/core/lib/dal/src/models/storage_transaction.rs index 01bbf4b4ff45..bce5e554f383 100644 --- a/core/lib/dal/src/models/storage_transaction.rs +++ b/core/lib/dal/src/models/storage_transaction.rs @@ -337,6 +337,7 @@ pub(crate) struct StorageTransactionReceipt { pub effective_gas_price: Option, pub contract_address: Option>, pub initiator_address: Vec, + pub block_timestamp: Option, } impl From for TransactionReceipt { diff --git a/core/lib/dal/src/transactions_web3_dal.rs b/core/lib/dal/src/transactions_web3_dal.rs index a73a383ff640..f207468d374c 100644 --- a/core/lib/dal/src/transactions_web3_dal.rs +++ b/core/lib/dal/src/transactions_web3_dal.rs @@ -43,7 +43,7 @@ impl TransactionsWeb3Dal<'_, '_> { // Clarification for first part of the query(`WITH` clause): // Looking for `ContractDeployed` event in the events table // to find the address of deployed contract - let mut receipts: Vec = sqlx::query_as!( + let st_receipts: Vec = sqlx::query_as!( StorageTransactionReceipt, r#" WITH @@ -75,7 +75,8 @@ impl TransactionsWeb3Dal<'_, '_> { transactions.gas_limit AS gas_limit, miniblocks.hash AS "block_hash", miniblocks.l1_batch_number AS "l1_batch_number?", - events.topic4 AS "contract_address?" + events.topic4 AS "contract_address?", + miniblocks.timestamp AS "block_timestamp?" FROM transactions JOIN miniblocks ON miniblocks.number = transactions.miniblock_number @@ -93,10 +94,13 @@ impl TransactionsWeb3Dal<'_, '_> { .instrument("get_transaction_receipts") .with_arg("hashes.len", &hashes.len()) .fetch_all(self.storage) - .await? - .into_iter() - .map(Into::into) - .collect(); + .await?; + + let block_timestamps: Vec> = + st_receipts.iter().map(|x| x.block_timestamp).collect(); + + let mut receipts: Vec = + st_receipts.into_iter().map(Into::into).collect(); let mut logs = self .storage @@ -110,7 +114,7 @@ impl TransactionsWeb3Dal<'_, '_> { .get_l2_to_l1_logs_by_hashes(hashes) .await?; - for receipt in &mut receipts { + for (receipt, block_timestamp) in receipts.iter_mut().zip(block_timestamps.into_iter()) { let logs_for_tx = logs.remove(&receipt.transaction_hash); if let Some(logs) = logs_for_tx { @@ -119,6 +123,7 @@ impl TransactionsWeb3Dal<'_, '_> { .map(|mut log| { log.block_hash = Some(receipt.block_hash); log.l1_batch_number = receipt.l1_batch_number; + log.block_timestamp = block_timestamp; log }) .collect(); diff --git a/core/lib/types/src/api/mod.rs b/core/lib/types/src/api/mod.rs index abf8288a8327..9c433a4afb85 100644 --- a/core/lib/types/src/api/mod.rs +++ b/core/lib/types/src/api/mod.rs @@ -443,6 +443,9 @@ pub struct Log { pub log_type: Option, /// Removed pub removed: Option, + /// L2 block timestamp + #[serde(rename = "blockTimestamp")] + pub block_timestamp: Option, } impl Log { diff --git a/core/lib/types/src/event/mod.rs b/core/lib/types/src/event/mod.rs index 055b41d77c7d..81e796097249 100644 --- a/core/lib/types/src/event/mod.rs +++ b/core/lib/types/src/event/mod.rs @@ -58,6 +58,7 @@ impl From<&VmEvent> for Log { transaction_log_index: None, log_type: None, removed: Some(false), + block_timestamp: None, } } } diff --git a/core/lib/types/src/protocol_upgrade.rs b/core/lib/types/src/protocol_upgrade.rs index c0d7267ebfae..2d7aa5c4b756 100644 --- a/core/lib/types/src/protocol_upgrade.rs +++ b/core/lib/types/src/protocol_upgrade.rs @@ -486,6 +486,7 @@ mod tests { transaction_log_index: Default::default(), log_type: Default::default(), removed: Default::default(), + block_timestamp: Default::default(), }; let decoded_op: GovernanceOperation = correct_log.clone().try_into().unwrap(); assert_eq!(decoded_op.calls.len(), 1); diff --git a/core/node/consistency_checker/src/tests/mod.rs b/core/node/consistency_checker/src/tests/mod.rs index 853090b1907d..13c1caec381a 100644 --- a/core/node/consistency_checker/src/tests/mod.rs +++ b/core/node/consistency_checker/src/tests/mod.rs @@ -382,6 +382,7 @@ fn l1_batch_commit_log(l1_batch: &L1BatchWithMetadata) -> Log { transaction_log_index: None, log_type: Some("mined".into()), removed: None, + block_timestamp: None, } } diff --git a/core/node/eth_watch/src/tests.rs b/core/node/eth_watch/src/tests.rs index 6b15c71bd140..773b7f62030a 100644 --- a/core/node/eth_watch/src/tests.rs +++ b/core/node/eth_watch/src/tests.rs @@ -505,6 +505,7 @@ fn tx_into_log(tx: L1Tx) -> Log { transaction_log_index: Some(0u64.into()), log_type: None, removed: None, + block_timestamp: None, } } @@ -549,6 +550,7 @@ fn upgrade_into_governor_log(upgrade: ProtocolUpgrade, eth_block: u64) -> Log { transaction_log_index: Some(0u64.into()), log_type: None, removed: None, + block_timestamp: None, } } diff --git a/core/tests/ts-integration/tests/api/web3.test.ts b/core/tests/ts-integration/tests/api/web3.test.ts index f306d3be43a4..a538eb3a6dff 100644 --- a/core/tests/ts-integration/tests/api/web3.test.ts +++ b/core/tests/ts-integration/tests/api/web3.test.ts @@ -744,6 +744,32 @@ describe('web3 API compatibility tests', () => { expect(logs[0].transactionHash).toEqual(tx.hash); }); + test('Should check getLogs returns block_timestamp', async () => { + // We're sending a transfer from the wallet, so we'll use a new account to make event unique. + let uniqueRecipient = testMaster.newEmptyAccount().address; + const tx = await alice.transfer({ + to: uniqueRecipient, + amount: 1, + token: l2Token + }); + const receipt = await tx.wait(); + const response = await alice.provider.send('eth_getLogs', [ + { + fromBlock: ethers.toBeHex(receipt.blockNumber), + toBlock: ethers.toBeHex(receipt.blockNumber), + address: l2Token, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + ethers.zeroPadValue(alice.address, 32), + ethers.zeroPadValue(uniqueRecipient, 32) + ] + } + ]); + expect(response).toHaveLength(1); + // TODO: switch to provider.getLogs once blockTimestamp is added to zksync ethers.js + expect(response[0].blockTimestamp).toBeDefined(); + }); + test('Should check getLogs endpoint works properly with block tags', async () => { const earliestLogs = alice.provider.send('eth_getLogs', [ {