From 3d047ea953d6fed4d0463fce60f743086f4a13b9 Mon Sep 17 00:00:00 2001 From: Marcin M <128217157+mm-zk@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:30:44 +0200 Subject: [PATCH] feat: Adding unstable RPC endpoint to return the execution_info (#2332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ * Execution info shows some details from the VM execution (like circutits used, pubdata etc) * This data was only stored in DB and not accessible outside - after this PR, it is available under `nstable_getTransactionExecutionInfo` ## Why ❔ * This allows us to do more advanced debugging of issues - especially for cases where we might not have access to the underlying database. * In the future, some parts of this might be migrated into a 'stable' RPC. ## Evidence ![image](https://github.com/matter-labs/zksync-era/assets/128217157/20da9e80-f7b3-4614-89f3-b09a774ffcf9) --- ...a9a184779646a16537df5b7cc54d0b4175d24.json | 22 ++++++++++ .../lib/dal/src/models/storage_transaction.rs | 8 ++++ core/lib/dal/src/transactions_web3_dal.rs | 40 ++++++++++++++++++- core/lib/types/src/api/mod.rs | 9 +++++ core/lib/web3_decl/src/namespaces/mod.rs | 7 ++-- core/lib/web3_decl/src/namespaces/unstable.rs | 23 +++++++++++ .../web3/backend_jsonrpsee/namespaces/mod.rs | 1 + .../backend_jsonrpsee/namespaces/unstable.rs | 19 +++++++++ core/node/api_server/src/web3/mod.rs | 14 +++++-- .../api_server/src/web3/namespaces/mod.rs | 4 +- .../src/web3/namespaces/unstable.rs | 33 +++++++++++++++ 11 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 core/lib/dal/.sqlx/query-53ab91ac4daebeb7d9d38018f31a9a184779646a16537df5b7cc54d0b4175d24.json create mode 100644 core/lib/web3_decl/src/namespaces/unstable.rs create mode 100644 core/node/api_server/src/web3/backend_jsonrpsee/namespaces/unstable.rs create mode 100644 core/node/api_server/src/web3/namespaces/unstable.rs diff --git a/core/lib/dal/.sqlx/query-53ab91ac4daebeb7d9d38018f31a9a184779646a16537df5b7cc54d0b4175d24.json b/core/lib/dal/.sqlx/query-53ab91ac4daebeb7d9d38018f31a9a184779646a16537df5b7cc54d0b4175d24.json new file mode 100644 index 000000000000..9694b9c662c1 --- /dev/null +++ b/core/lib/dal/.sqlx/query-53ab91ac4daebeb7d9d38018f31a9a184779646a16537df5b7cc54d0b4175d24.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n transactions.execution_info\n FROM\n transactions\n WHERE\n transactions.hash = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "execution_info", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Bytea" + ] + }, + "nullable": [ + false + ] + }, + "hash": "53ab91ac4daebeb7d9d38018f31a9a184779646a16537df5b7cc54d0b4175d24" +} diff --git a/core/lib/dal/src/models/storage_transaction.rs b/core/lib/dal/src/models/storage_transaction.rs index 1dfd5f4b6a0e..01bbf4b4ff45 100644 --- a/core/lib/dal/src/models/storage_transaction.rs +++ b/core/lib/dal/src/models/storage_transaction.rs @@ -1,6 +1,7 @@ use std::{convert::TryInto, str::FromStr}; use bigdecimal::Zero; +use serde_json::Value; use sqlx::types::chrono::{DateTime, NaiveDateTime, Utc}; use zksync_types::{ api::{self, TransactionDetails, TransactionReceipt, TransactionStatus}, @@ -396,6 +397,13 @@ impl From for TransactionReceipt { } } +/// Details of the transaction execution. +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct StorageTransactionExecutionInfo { + /// This is an opaque JSON field, with VM version specific contents. + pub execution_info: Value, +} + #[derive(Debug, Clone, sqlx::FromRow)] pub(crate) struct StorageTransactionDetails { pub is_priority: bool, diff --git a/core/lib/dal/src/transactions_web3_dal.rs b/core/lib/dal/src/transactions_web3_dal.rs index 2d380a8059a6..a73a383ff640 100644 --- a/core/lib/dal/src/transactions_web3_dal.rs +++ b/core/lib/dal/src/transactions_web3_dal.rs @@ -16,7 +16,7 @@ use zksync_types::{ use crate::{ models::storage_transaction::{ StorageApiTransaction, StorageTransaction, StorageTransactionDetails, - StorageTransactionReceipt, + StorageTransactionExecutionInfo, StorageTransactionReceipt, }, Core, CoreDal, }; @@ -151,6 +151,29 @@ impl TransactionsWeb3Dal<'_, '_> { .await } + pub async fn get_unstable_transaction_execution_info( + &mut self, + hash: H256, + ) -> DalResult> { + let row = sqlx::query_as!( + StorageTransactionExecutionInfo, + r#" + SELECT + transactions.execution_info + FROM + transactions + WHERE + transactions.hash = $1 + "#, + hash.as_bytes() + ) + .instrument("get_unstable_transaction_execution_info") + .with_arg("hash", &hash) + .fetch_optional(self.storage) + .await?; + Ok(row.map(|entry| entry.execution_info)) + } + async fn get_transactions_inner( &mut self, selector: TransactionSelector<'_>, @@ -550,6 +573,21 @@ mod tests { .get_transaction_by_hash(H256::zero(), L2ChainId::from(270)) .await; assert!(web3_tx.unwrap().is_none()); + + let execution_info = conn + .transactions_web3_dal() + .get_unstable_transaction_execution_info(tx_hash) + .await + .unwrap() + .expect("Transaction execution info is missing in the DAL"); + + // Check that execution info has at least the circuit statistics field. + // If this assertion fails because the transaction execution info format + // has changed, replace circuit_statistic with any other valid field + assert!( + execution_info.get("circuit_statistic").is_some(), + "Missing circuit_statistics field" + ); } #[tokio::test] diff --git a/core/lib/types/src/api/mod.rs b/core/lib/types/src/api/mod.rs index 0617f47268aa..abf8288a8327 100644 --- a/core/lib/types/src/api/mod.rs +++ b/core/lib/types/src/api/mod.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Utc}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde_json::Value; use strum::Display; use zksync_basic_types::{ web3::{AccessList, Bytes, Index}, @@ -821,6 +822,14 @@ pub struct ApiStorageLog { pub written_value: U256, } +/// Raw transaction execution data. +/// Data is taken from `TransactionExecutionMetrics`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionExecutionInfo { + pub execution_info: Value, +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/lib/web3_decl/src/namespaces/mod.rs b/core/lib/web3_decl/src/namespaces/mod.rs index 76445f9a4fd8..f3b5c8a9aaee 100644 --- a/core/lib/web3_decl/src/namespaces/mod.rs +++ b/core/lib/web3_decl/src/namespaces/mod.rs @@ -1,13 +1,13 @@ pub use self::{ debug::DebugNamespaceClient, en::EnNamespaceClient, eth::EthNamespaceClient, - net::NetNamespaceClient, snapshots::SnapshotsNamespaceClient, web3::Web3NamespaceClient, - zks::ZksNamespaceClient, + net::NetNamespaceClient, snapshots::SnapshotsNamespaceClient, + unstable::UnstableNamespaceClient, web3::Web3NamespaceClient, zks::ZksNamespaceClient, }; #[cfg(feature = "server")] pub use self::{ debug::DebugNamespaceServer, en::EnNamespaceServer, eth::EthNamespaceServer, eth::EthPubSubServer, net::NetNamespaceServer, snapshots::SnapshotsNamespaceServer, - web3::Web3NamespaceServer, zks::ZksNamespaceServer, + unstable::UnstableNamespaceServer, web3::Web3NamespaceServer, zks::ZksNamespaceServer, }; mod debug; @@ -15,5 +15,6 @@ mod en; mod eth; mod net; mod snapshots; +mod unstable; mod web3; mod zks; diff --git a/core/lib/web3_decl/src/namespaces/unstable.rs b/core/lib/web3_decl/src/namespaces/unstable.rs new file mode 100644 index 000000000000..4996813a9855 --- /dev/null +++ b/core/lib/web3_decl/src/namespaces/unstable.rs @@ -0,0 +1,23 @@ +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; +use zksync_types::{api::TransactionExecutionInfo, H256}; + +use crate::client::{ForNetwork, L2}; + +/// RPCs in this namespace are experimental, and their interface is unstable, and it WILL change. +#[cfg_attr( + feature = "server", + rpc(server, client, namespace = "unstable", client_bounds(Self: ForNetwork)) +)] +#[cfg_attr( + not(feature = "server"), + rpc(client, namespace = "unstable", client_bounds(Self: ForNetwork)) +)] +pub trait UnstableNamespace { + #[method(name = "getTransactionExecutionInfo")] + async fn transaction_execution_info( + &self, + hash: H256, + ) -> RpcResult>; +} diff --git a/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/mod.rs b/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/mod.rs index 3f0f043f8d4a..1d00e90b0e84 100644 --- a/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/mod.rs +++ b/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/mod.rs @@ -3,5 +3,6 @@ pub mod en; pub mod eth; pub mod net; pub mod snapshots; +pub mod unstable; pub mod web3; pub mod zks; diff --git a/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/unstable.rs b/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/unstable.rs new file mode 100644 index 000000000000..6abaa718a050 --- /dev/null +++ b/core/node/api_server/src/web3/backend_jsonrpsee/namespaces/unstable.rs @@ -0,0 +1,19 @@ +use zksync_types::{api::TransactionExecutionInfo, H256}; +use zksync_web3_decl::{ + jsonrpsee::core::{async_trait, RpcResult}, + namespaces::UnstableNamespaceServer, +}; + +use crate::web3::namespaces::UnstableNamespace; + +#[async_trait] +impl UnstableNamespaceServer for UnstableNamespace { + async fn transaction_execution_info( + &self, + hash: H256, + ) -> RpcResult> { + self.transaction_execution_info_impl(hash) + .await + .map_err(|err| self.current_method().map_err(err)) + } +} diff --git a/core/node/api_server/src/web3/mod.rs b/core/node/api_server/src/web3/mod.rs index 7b2dec7abb35..19e103c97998 100644 --- a/core/node/api_server/src/web3/mod.rs +++ b/core/node/api_server/src/web3/mod.rs @@ -24,7 +24,8 @@ use zksync_web3_decl::{ }, namespaces::{ DebugNamespaceServer, EnNamespaceServer, EthNamespaceServer, EthPubSubServer, - NetNamespaceServer, SnapshotsNamespaceServer, Web3NamespaceServer, ZksNamespaceServer, + NetNamespaceServer, SnapshotsNamespaceServer, UnstableNamespaceServer, Web3NamespaceServer, + ZksNamespaceServer, }, types::Filter, }; @@ -37,8 +38,8 @@ use self::{ mempool_cache::MempoolCache, metrics::API_METRICS, namespaces::{ - DebugNamespace, EnNamespace, EthNamespace, NetNamespace, SnapshotsNamespace, Web3Namespace, - ZksNamespace, + DebugNamespace, EnNamespace, EthNamespace, NetNamespace, SnapshotsNamespace, + UnstableNamespace, Web3Namespace, ZksNamespace, }, pubsub::{EthSubscribe, EthSubscriptionIdProvider, PubSubEvent}, state::{Filters, InternalApiConfig, RpcState, SealedL2BlockNumber}, @@ -98,6 +99,7 @@ pub enum Namespace { En, Pubsub, Snapshots, + Unstable, } impl Namespace { @@ -407,9 +409,13 @@ impl ApiServer { .context("cannot merge en namespace")?; } if namespaces.contains(&Namespace::Snapshots) { - rpc.merge(SnapshotsNamespace::new(rpc_state).into_rpc()) + rpc.merge(SnapshotsNamespace::new(rpc_state.clone()).into_rpc()) .context("cannot merge snapshots namespace")?; } + if namespaces.contains(&Namespace::Unstable) { + rpc.merge(UnstableNamespace::new(rpc_state).into_rpc()) + .context("cannot merge unstable namespace")?; + } Ok(rpc) } diff --git a/core/node/api_server/src/web3/namespaces/mod.rs b/core/node/api_server/src/web3/namespaces/mod.rs index b9355f7181ff..bf35cac0409e 100644 --- a/core/node/api_server/src/web3/namespaces/mod.rs +++ b/core/node/api_server/src/web3/namespaces/mod.rs @@ -6,10 +6,12 @@ mod en; pub(crate) mod eth; mod net; mod snapshots; +mod unstable; mod web3; mod zks; pub(super) use self::{ debug::DebugNamespace, en::EnNamespace, eth::EthNamespace, net::NetNamespace, - snapshots::SnapshotsNamespace, web3::Web3Namespace, zks::ZksNamespace, + snapshots::SnapshotsNamespace, unstable::UnstableNamespace, web3::Web3Namespace, + zks::ZksNamespace, }; diff --git a/core/node/api_server/src/web3/namespaces/unstable.rs b/core/node/api_server/src/web3/namespaces/unstable.rs new file mode 100644 index 000000000000..b46ecd6dc530 --- /dev/null +++ b/core/node/api_server/src/web3/namespaces/unstable.rs @@ -0,0 +1,33 @@ +use zksync_dal::{CoreDal, DalError}; +use zksync_types::api::TransactionExecutionInfo; +use zksync_web3_decl::{error::Web3Error, types::H256}; + +use crate::web3::{backend_jsonrpsee::MethodTracer, RpcState}; + +#[derive(Debug)] +pub(crate) struct UnstableNamespace { + state: RpcState, +} + +impl UnstableNamespace { + pub fn new(state: RpcState) -> Self { + Self { state } + } + + pub(crate) fn current_method(&self) -> &MethodTracer { + &self.state.current_method + } + + pub async fn transaction_execution_info_impl( + &self, + hash: H256, + ) -> Result, Web3Error> { + let mut storage = self.state.acquire_connection().await?; + Ok(storage + .transactions_web3_dal() + .get_unstable_transaction_execution_info(hash) + .await + .map_err(DalError::generalize)? + .map(|execution_info| TransactionExecutionInfo { execution_info })) + } +}