From 00eafaf48c9ba61efc54e3564f2cc7b35b607983 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 8 May 2024 15:44:02 +0300 Subject: [PATCH 01/12] Sketch unified RPC client backend --- core/bin/external_node/src/config/mod.rs | 6 +- core/bin/external_node/src/helpers.rs | 11 +- core/bin/external_node/src/init.rs | 4 +- core/bin/external_node/src/main.rs | 18 +-- core/bin/external_node/src/tests.rs | 6 +- .../external_node/src/version_sync_task.rs | 8 +- core/lib/snapshots_applier/src/lib.rs | 4 +- core/lib/web3_decl/src/client/boxed.rs | 111 ++++++++++-------- core/lib/web3_decl/src/client/mock.rs | 49 +++----- core/lib/web3_decl/src/client/mod.rs | 85 +++++++++----- core/lib/web3_decl/src/client/network.rs | 30 +++++ core/lib/web3_decl/src/client/shared.rs | 89 ++++++++++++++ core/lib/web3_decl/src/client/tests.rs | 4 +- core/lib/web3_decl/src/namespaces/debug.rs | 9 +- core/lib/web3_decl/src/namespaces/en.rs | 6 +- core/lib/web3_decl/src/namespaces/eth.rs | 13 +- .../web3_decl/src/namespaces/eth_subscribe.rs | 1 - core/lib/web3_decl/src/namespaces/mod.rs | 17 ++- core/lib/web3_decl/src/namespaces/net.rs | 6 +- .../lib/web3_decl/src/namespaces/snapshots.rs | 6 +- core/lib/web3_decl/src/namespaces/web3.rs | 6 +- core/lib/web3_decl/src/namespaces/zks.rs | 9 +- .../src/api_server/tx_sender/proxy.rs | 6 +- .../src/api_server/web3/tests/debug.rs | 29 ++++- .../src/api_server/web3/tests/filters.rs | 24 +++- .../src/api_server/web3/tests/mod.rs | 108 ++++++++++++----- .../src/api_server/web3/tests/snapshots.rs | 6 +- .../src/api_server/web3/tests/vm.rs | 42 +++++-- .../src/api_server/web3/tests/ws.rs | 28 ++--- core/lib/zksync_core/src/consensus/era.rs | 4 +- core/lib/zksync_core/src/consensus/fetcher.rs | 4 +- .../lib/zksync_core/src/consensus/testonly.rs | 12 +- .../lib/zksync_core/src/reorg_detector/mod.rs | 6 +- .../sync_layer/batch_status_updater/mod.rs | 6 +- core/lib/zksync_core/src/sync_layer/client.rs | 4 +- .../zksync_core/src/sync_layer/sync_state.rs | 7 +- .../src/l1_gas_price/main_node_fetcher.rs | 8 +- .../src/implementations/layers/consensus.rs | 4 +- .../resources/main_node_client.rs | 4 +- core/tests/loadnext/src/account/mod.rs | 9 +- core/tests/loadnext/src/account_pool.rs | 20 ++-- core/tests/loadnext/src/sdk/wallet.rs | 13 +- 42 files changed, 566 insertions(+), 276 deletions(-) create mode 100644 core/lib/web3_decl/src/client/network.rs create mode 100644 core/lib/web3_decl/src/client/shared.rs delete mode 100644 core/lib/web3_decl/src/namespaces/eth_subscribe.rs diff --git a/core/bin/external_node/src/config/mod.rs b/core/bin/external_node/src/config/mod.rs index 138b7a12776..cec55745b22 100644 --- a/core/bin/external_node/src/config/mod.rs +++ b/core/bin/external_node/src/config/mod.rs @@ -32,7 +32,7 @@ use zksync_types::{ api::BridgeAddresses, fee_model::FeeParams, url::SensitiveUrl, ETHEREUM_ADDRESS, }; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, error::ClientRpcContext, jsonrpsee::{core::ClientError, types::error::ErrorCode}, namespaces::{EnNamespaceClient, EthNamespaceClient, ZksNamespaceClient}, @@ -70,7 +70,7 @@ pub(crate) struct RemoteENConfig { } impl RemoteENConfig { - pub async fn fetch(client: &BoxedL2Client) -> anyhow::Result { + pub async fn fetch(client: &DynClient) -> anyhow::Result { let bridges = client .get_bridge_contracts() .rpc_context("get_bridge_contracts") @@ -821,7 +821,7 @@ impl ExternalNodeConfig { pub async fn new( required: RequiredENConfig, optional: OptionalENConfig, - main_node_client: &BoxedL2Client, + main_node_client: &DynClient, eth_client: &dyn EthInterface, ) -> anyhow::Result { let experimental = envy::prefixed("EN_EXPERIMENTAL_") diff --git a/core/bin/external_node/src/helpers.rs b/core/bin/external_node/src/helpers.rs index 2bc580c43fe..5443675f48f 100644 --- a/core/bin/external_node/src/helpers.rs +++ b/core/bin/external_node/src/helpers.rs @@ -1,14 +1,17 @@ //! Miscellaneous helpers for the EN. use zksync_health_check::{async_trait, CheckHealth, Health, HealthStatus}; -use zksync_web3_decl::{client::BoxedL2Client, namespaces::EthNamespaceClient}; +use zksync_web3_decl::{ + client::{DynClient, L2}, + namespaces::EthNamespaceClient, +}; /// Main node health check. #[derive(Debug)] -pub(crate) struct MainNodeHealthCheck(BoxedL2Client); +pub(crate) struct MainNodeHealthCheck(Box>); -impl From for MainNodeHealthCheck { - fn from(client: BoxedL2Client) -> Self { +impl From>> for MainNodeHealthCheck { + fn from(client: Box>) -> Self { Self(client.for_component("main_node_health_check")) } } diff --git a/core/bin/external_node/src/init.rs b/core/bin/external_node/src/init.rs index fb8eee9bce0..1703556720d 100644 --- a/core/bin/external_node/src/init.rs +++ b/core/bin/external_node/src/init.rs @@ -10,7 +10,7 @@ use zksync_health_check::AppHealthCheck; use zksync_object_store::ObjectStoreFactory; use zksync_shared_metrics::{SnapshotRecoveryStage, APP_METRICS}; use zksync_snapshots_applier::{SnapshotsApplierConfig, SnapshotsApplierTask}; -use zksync_web3_decl::client::BoxedL2Client; +use zksync_web3_decl::client::{DynClient, L2}; use crate::config::SnapshotsRecoveryConfig; @@ -24,7 +24,7 @@ enum InitDecision { pub(crate) async fn ensure_storage_initialized( pool: ConnectionPool, - main_node_client: BoxedL2Client, + main_node_client: Box>, app_health: &AppHealthCheck, l2_chain_id: L2ChainId, consider_snapshot_recovery: bool, diff --git a/core/bin/external_node/src/main.rs b/core/bin/external_node/src/main.rs index 721a117378a..6610491de1f 100644 --- a/core/bin/external_node/src/main.rs +++ b/core/bin/external_node/src/main.rs @@ -56,7 +56,7 @@ use zksync_storage::RocksDB; use zksync_types::L2ChainId; use zksync_utils::wait_for_tasks::ManagedTasks; use zksync_web3_decl::{ - client::{BoxedL2Client, L2Client}, + client::{Client, DynClient, L2}, jsonrpsee, namespaces::EnNamespaceClient, }; @@ -87,7 +87,7 @@ async fn build_state_keeper( state_keeper_db_path: String, config: &ExternalNodeConfig, connection_pool: ConnectionPool, - main_node_client: BoxedL2Client, + main_node_client: Box>, output_handler: OutputHandler, stop_receiver: watch::Receiver, chain_id: L2ChainId, @@ -212,7 +212,7 @@ async fn run_tree( async fn run_core( config: &ExternalNodeConfig, connection_pool: ConnectionPool, - main_node_client: BoxedL2Client, + main_node_client: Box>, eth_client: Box, task_handles: &mut Vec>>, app_health: &AppHealthCheck, @@ -415,7 +415,7 @@ async fn run_api( stop_receiver: watch::Receiver, sync_state: SyncState, tree_reader: Option>, - main_node_client: BoxedL2Client, + main_node_client: Box>, singleton_pool_builder: &ConnectionPoolBuilder, fee_params_fetcher: Arc, components: &HashSet, @@ -594,7 +594,7 @@ async fn init_tasks( config: &ExternalNodeConfig, connection_pool: ConnectionPool, singleton_pool_builder: ConnectionPoolBuilder, - main_node_client: BoxedL2Client, + main_node_client: Box>, eth_client: Box, task_handles: &mut Vec>>, app_health: &AppHealthCheck, @@ -821,11 +821,11 @@ async fn main() -> anyhow::Result<()> { // Build L1 and L2 clients. let main_node_url = &required_config.main_node_url; tracing::info!("Main node URL is: {main_node_url:?}"); - let main_node_client = L2Client::http(main_node_url.clone()) + let main_node_client = Client::::http(main_node_url.clone()) .context("Failed creating JSON-RPC client for main node")? .with_allowed_requests_per_second(optional_config.main_node_rate_limit_rps) .build(); - let main_node_client = BoxedL2Client::new(main_node_client); + let main_node_client = Box::new(main_node_client) as Box>; let eth_client_url = &required_config.eth_client_url; let eth_client = Box::new(QueryClient::new(eth_client_url.clone())?); @@ -833,7 +833,7 @@ async fn main() -> anyhow::Result<()> { let mut config = ExternalNodeConfig::new( required_config, optional_config, - &main_node_client, + main_node_client.as_ref(), eth_client.as_ref(), ) .await @@ -897,7 +897,7 @@ async fn run_node( config: &ExternalNodeConfig, connection_pool: ConnectionPool, singleton_pool_builder: ConnectionPoolBuilder, - main_node_client: BoxedL2Client, + main_node_client: Box>, eth_client: Box, ) -> anyhow::Result<()> { tracing::warn!("The external node is in the alpha phase, and should be used with caution."); diff --git a/core/bin/external_node/src/tests.rs b/core/bin/external_node/src/tests.rs index d1ab1ab00b6..38eb323811d 100644 --- a/core/bin/external_node/src/tests.rs +++ b/core/bin/external_node/src/tests.rs @@ -6,7 +6,7 @@ use zksync_basic_types::protocol_version::ProtocolVersionId; use zksync_eth_client::clients::MockEthereum; use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; use zksync_types::{api, ethabi, fee_model::FeeParams, L1BatchNumber, L2BlockNumber, H256}; -use zksync_web3_decl::client::{BoxedL2Client, MockL2Client}; +use zksync_web3_decl::client::MockClient; use super::*; @@ -131,7 +131,7 @@ async fn external_node_basics(components_str: &'static str) { let diamond_proxy_addr = config.remote.diamond_proxy_addr; - let l2_client = MockL2Client::new(move |method, params| { + let l2_client = MockClient::::new(move |method, params| { tracing::info!("Called L2 client: {method}({params:?})"); match method { "zks_L1BatchNumber" => Ok(serde_json::json!("0x0")), @@ -161,7 +161,7 @@ async fn external_node_basics(components_str: &'static str) { _ => panic!("Unexpected call: {method}({params:?})"), } }); - let l2_client = BoxedL2Client::new(l2_client); + let l2_client = Box::new(l2_client); let eth_client = MockEthereum::default().with_call_handler(move |call, _| { tracing::info!("L1 call: {call:?}"); diff --git a/core/bin/external_node/src/version_sync_task.rs b/core/bin/external_node/src/version_sync_task.rs index 01926cad373..3064a15c1f6 100644 --- a/core/bin/external_node/src/version_sync_task.rs +++ b/core/bin/external_node/src/version_sync_task.rs @@ -5,12 +5,12 @@ use zksync_basic_types::{L1BatchNumber, L2BlockNumber}; use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_types::ProtocolVersionId; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, namespaces::{EnNamespaceClient, ZksNamespaceClient}, }; pub async fn get_l1_batch_remote_protocol_version( - main_node_client: &BoxedL2Client, + main_node_client: &DynClient, l1_batch_number: L1BatchNumber, ) -> anyhow::Result> { let Some((miniblock, _)) = main_node_client.get_l2_block_range(l1_batch_number).await? else { @@ -25,7 +25,7 @@ pub async fn get_l1_batch_remote_protocol_version( // Synchronizes protocol version in `l1_batches` and `miniblocks` tables between EN and main node. pub async fn sync_versions( connection_pool: ConnectionPool, - main_node_client: BoxedL2Client, + main_node_client: Box>, ) -> anyhow::Result<()> { tracing::info!("Starting syncing protocol version of blocks"); @@ -54,7 +54,7 @@ pub async fn sync_versions( } let right_bound_remote_version = - get_l1_batch_remote_protocol_version(&main_node_client, right_bound).await?; + get_l1_batch_remote_protocol_version(main_node_client.as_ref(), right_bound).await?; if right_bound_remote_version != Some(ProtocolVersionId::Version22) { anyhow::bail!("Remote protocol versions should be v22 for the first local v22 batch, got {right_bound_remote_version:?}"); } diff --git a/core/lib/snapshots_applier/src/lib.rs b/core/lib/snapshots_applier/src/lib.rs index fcfd0909c3e..151f70c2166 100644 --- a/core/lib/snapshots_applier/src/lib.rs +++ b/core/lib/snapshots_applier/src/lib.rs @@ -20,7 +20,7 @@ use zksync_types::{ }; use zksync_utils::bytecode::hash_bytecode; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, error::{ClientRpcContext, EnrichedClientError, EnrichedClientResult}, jsonrpsee::core::client, namespaces::{EnNamespaceClient, SnapshotsNamespaceClient, ZksNamespaceClient}, @@ -135,7 +135,7 @@ pub trait SnapshotsApplierMainNodeClient: fmt::Debug + Send + Sync { } #[async_trait] -impl SnapshotsApplierMainNodeClient for BoxedL2Client { +impl SnapshotsApplierMainNodeClient for Box> { async fn fetch_l1_batch_details( &self, number: L1BatchNumber, diff --git a/core/lib/web3_decl/src/client/boxed.rs b/core/lib/web3_decl/src/client/boxed.rs index 251994e1f6e..ba091fa99f3 100644 --- a/core/lib/web3_decl/src/client/boxed.rs +++ b/core/lib/web3_decl/src/client/boxed.rs @@ -9,10 +9,15 @@ use jsonrpsee::core::{ }; use serde::de::DeserializeOwned; -use super::TaggedClient; +use super::{ForNetwork, Network}; + +pub trait TaggedClient: ForNetwork { + /// Sets the component operating this client. This is used in logging etc. + fn for_component(self, component_name: &'static str) -> Self; +} #[derive(Debug)] -struct RawParams(Option>); +pub struct RawParams(Option>); impl RawParams { fn new(params: impl ToRpcParams) -> Result { @@ -26,18 +31,18 @@ impl ToRpcParams for RawParams { } } -/// Object-safe version of [`ClientT`] + [`Clone`] + [`TaggedClient`]. +/// Object-safe version of [`ClientT`] + [`Clone`]. /// /// The implementation is fairly straightforward: [`RawParams`] is used as a catch-all params type, /// and `serde_json::Value` is used as a catch-all response type. #[async_trait] -trait ObjectSafeClient: 'static + Send + Sync + fmt::Debug { - fn clone_boxed(&self) -> Box; +pub trait ObjectSafeClient: 'static + Send + Sync + fmt::Debug + ForNetwork { + fn clone_boxed(&self) -> Box>; - fn for_component_boxed( + fn for_component( self: Box, component_name: &'static str, - ) -> Box; + ) -> Box>; async fn notification(&self, method: &str, params: RawParams) -> Result<(), Error>; @@ -54,15 +59,15 @@ impl ObjectSafeClient for C where C: 'static + Send + Sync + Clone + fmt::Debug + ClientT + TaggedClient, { - fn clone_boxed(&self) -> Box { + fn clone_boxed(&self) -> Box::Net>> { Box::new(self.clone()) } - fn for_component_boxed( + fn for_component( self: Box, component_name: &'static str, - ) -> Box { - Box::new(self.for_component(component_name)) + ) -> Box::Net>> { + Box::new(TaggedClient::for_component(*self, component_name)) } async fn notification(&self, method: &str, params: RawParams) -> Result<(), Error> { @@ -81,46 +86,21 @@ where } } -/// Boxed version of an L2 client. -/// -/// For now, it has two implementations: -/// -/// - [`L2Client`](super::L2Client) is the main implementation based on HTTP JSON-RPC -/// - [`MockL2Client`](super::MockL2Client) is a mock implementation. -#[derive(Debug)] -pub struct BoxedL2Client(Box); +pub type DynClient = dyn ObjectSafeClient; -impl Clone for BoxedL2Client { +impl Clone for Box> { fn clone(&self) -> Self { - Self(self.0.clone_boxed()) - } -} - -impl BoxedL2Client { - /// Boxes the provided client. - pub fn new(client: C) -> Self - where - C: 'static + Send + Sync + Clone + fmt::Debug + ClientT + TaggedClient, - { - Self(Box::new(client)) - } - - /// Tags this client with a component label, which will be shown in logs etc. - pub fn for_component(self, component_name: &'static str) -> Self { - Self(self.0.for_component_boxed(component_name)) + self.clone_boxed() } } #[async_trait] -impl ClientT for BoxedL2Client { +impl ClientT for &DynClient { async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where Params: ToRpcParams + Send, { - self.0 - .as_ref() - .notification(method, RawParams::new(params)?) - .await + (**self).notification(method, RawParams::new(params)?).await } async fn request(&self, method: &str, params: Params) -> Result @@ -128,11 +108,7 @@ impl ClientT for BoxedL2Client { R: DeserializeOwned, Params: ToRpcParams + Send, { - let raw_response = self - .0 - .as_ref() - .request(method, RawParams::new(params)?) - .await?; + let raw_response = (**self).request(method, RawParams::new(params)?).await?; serde_json::from_value(raw_response).map_err(Error::ParseError) } @@ -143,7 +119,7 @@ impl ClientT for BoxedL2Client { where R: DeserializeOwned + fmt::Debug + 'a, { - let raw_responses = self.0.as_ref().batch_request(batch).await?; + let raw_responses = (**self).batch_request(batch).await?; let mut successful_calls = 0; let mut failed_calls = 0; let mut responses = Vec::with_capacity(raw_responses.len()); @@ -167,20 +143,55 @@ impl ClientT for BoxedL2Client { } } +// Delegates to the above `&DynClient` implementation. +#[async_trait] +impl ClientT for Box> { + async fn notification(&self, method: &str, params: Params) -> Result<(), Error> + where + Params: ToRpcParams + Send, + { + ClientT::notification(&self.as_ref(), method, params).await + } + + async fn request(&self, method: &str, params: Params) -> Result + where + R: DeserializeOwned, + Params: ToRpcParams + Send, + { + ClientT::request(&self.as_ref(), method, params).await + } + + async fn batch_request<'a, R>( + &self, + batch: BatchRequestBuilder<'a>, + ) -> Result, Error> + where + R: DeserializeOwned + fmt::Debug + 'a, + { + ClientT::batch_request(&self.as_ref(), batch).await + } +} + #[cfg(test)] mod tests { use super::*; - use crate::{client::MockL2Client, namespaces::EthNamespaceClient}; + use crate::{ + client::{MockClient, L2}, + namespaces::EthNamespaceClient, + }; #[tokio::test] async fn boxing_mock_client() { - let client = MockL2Client::new(|method, params| { + let client = MockClient::new(|method, params| { assert_eq!(method, "eth_blockNumber"); assert_eq!(params, serde_json::Value::Null); Ok(serde_json::json!("0x42")) }); - let client = BoxedL2Client::new(client); + let client = Box::new(client) as Box>; + let block_number = client.get_block_number().await.unwrap(); assert_eq!(block_number, 0x42.into()); + let block_number = client.as_ref().get_block_number().await.unwrap(); + assert_eq!(block_number, 0x42.into()); } } diff --git a/core/lib/web3_decl/src/client/mock.rs b/core/lib/web3_decl/src/client/mock.rs index ed7aedc3530..f2de14ac8cc 100644 --- a/core/lib/web3_decl/src/client/mock.rs +++ b/core/lib/web3_decl/src/client/mock.rs @@ -1,17 +1,17 @@ //! Mock L2 client implementation. -use std::{fmt, future::Future, pin::Pin, sync::Arc}; +use std::{fmt, future::Future, marker::PhantomData, pin::Pin, sync::Arc}; use async_trait::async_trait; use futures::future; use jsonrpsee::core::{ - client::{BatchResponse, ClientT, Error, Subscription, SubscriptionClientT}, + client::{BatchResponse, ClientT, Error}, params::BatchRequestBuilder, traits::ToRpcParams, }; use serde::de::DeserializeOwned; -use super::TaggedClient; +use super::{ForNetwork, Network, TaggedClient}; type MockHandleResult<'a> = Pin> + Send + 'a>>; @@ -20,12 +20,13 @@ type RequestHandler = dyn Fn(&str, serde_json::Value) -> MockHandleResult<'_> + /// Mock L2 client implementation. For now, it only mocks requests and batch requests; all other /// interactions with the client will panic. #[derive(Clone)] -pub struct MockL2Client { +pub struct MockClient { request_handler: Arc, component_name: &'static str, + _network: PhantomData, } -impl fmt::Debug for MockL2Client { +impl fmt::Debug for MockClient { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("MockL2Client") @@ -33,7 +34,7 @@ impl fmt::Debug for MockL2Client { } } -impl MockL2Client { +impl MockClient { /// Creates an L2 client based on the provided request handler. pub fn new(request_handler: F) -> Self where @@ -44,6 +45,7 @@ impl MockL2Client { Box::pin(future::ready(request_handler(method, params))) }), component_name: "", + _network: PhantomData, } } @@ -55,11 +57,16 @@ impl MockL2Client { Self { request_handler: Arc::new(request_handler), component_name: "", + _network: PhantomData, } } } -impl TaggedClient for MockL2Client { +impl ForNetwork for MockClient { + type Net = Net; +} + +impl TaggedClient for MockClient { fn for_component(mut self, component_name: &'static str) -> Self { self.component_name = component_name; self @@ -67,7 +74,7 @@ impl TaggedClient for MockL2Client { } #[async_trait] -impl ClientT for MockL2Client { +impl ClientT for MockClient { async fn notification(&self, _method: &str, _params: Params) -> Result<(), Error> where Params: ToRpcParams + Send, @@ -124,29 +131,3 @@ impl ClientT for MockL2Client { )) } } - -#[async_trait] -impl SubscriptionClientT for MockL2Client { - async fn subscribe<'a, Notif, Params>( - &self, - _subscribe_method: &'a str, - _params: Params, - _unsubscribe_method: &'a str, - ) -> Result, Error> - where - Params: ToRpcParams + Send, - Notif: DeserializeOwned, - { - unimplemented!("never used in the codebase") - } - - async fn subscribe_to_method<'a, Notif>( - &self, - _method: &'a str, - ) -> Result, Error> - where - Notif: DeserializeOwned, - { - unimplemented!("never used in the codebase") - } -} diff --git a/core/lib/web3_decl/src/client/mod.rs b/core/lib/web3_decl/src/client/mod.rs index 8fe342c20ad..2f78a1e4012 100644 --- a/core/lib/web3_decl/src/client/mod.rs +++ b/core/lib/web3_decl/src/client/mod.rs @@ -3,9 +3,9 @@ //! //! # Overview //! -//! - [`L2Client`] is the main client implementation. It's parameterized by the transport (e.g., HTTP or WS), +//! - [`Client`] is the main client implementation. It's parameterized by the transport (e.g., HTTP or WS), //! with HTTP being the default option. -//! - [`MockL2Client`] is a mock client useful for testing. Bear in mind that because of the client being generic, +//! - [`MockClient`] is a mock client useful for testing. Bear in mind that because of the client being generic, //! mock tooling is fairly low-level. Prefer defining a domain-specific wrapper trait for the client functionality and mock it //! where it's possible. //! - [`BoxedL2Client`] is a generic client (essentially, a wrapper around a trait object). Use it for dependency injection @@ -15,6 +15,7 @@ use std::{ any, collections::HashSet, fmt, + marker::PhantomData, num::NonZeroUsize, sync::{Arc, Mutex}, time::Duration, @@ -28,17 +29,25 @@ use jsonrpsee::{ traits::ToRpcParams, }, http_client::{HttpClient, HttpClientBuilder}, + ws_client, }; use serde::de::DeserializeOwned; use tokio::time::Instant; use zksync_types::url::SensitiveUrl; use self::metrics::{L2ClientMetrics, METRICS}; -pub use self::{boxed::BoxedL2Client, mock::MockL2Client}; +pub use self::{ + boxed::{DynClient, TaggedClient}, + mock::MockClient, + network::{ForNetwork, Network, L1, L2}, + shared::Shared, +}; mod boxed; mod metrics; mod mock; +mod network; +mod shared; #[cfg(test)] mod tests; @@ -82,17 +91,12 @@ impl fmt::Display for CallOrigin<'_> { } } -/// Trait encapsulating requirements for the L2 client base. -pub trait L2ClientBase: SubscriptionClientT + Clone + fmt::Debug + Send + Sync + 'static {} - -impl L2ClientBase for T {} +/// Trait encapsulating requirements for the client base. +pub trait ClientBase: ClientT + Clone + fmt::Debug + Send + Sync + 'static {} -pub trait TaggedClient { - /// Sets the component operating this client. This is used in logging etc. - fn for_component(self, component_name: &'static str) -> Self; -} +impl ClientBase for T {} -/// JSON-RPC client for the main node with built-in middleware support. +/// JSON-RPC client for the main node or Ethereum node with built-in middleware support. /// /// The client should be used instead of `HttpClient` etc. A single instance of the client should be built /// and shared among all tasks run by the node in order to correctly rate-limit requests. @@ -107,18 +111,22 @@ pub trait TaggedClient { // [a boxed cloneable version of services](https://docs.rs/tower/latest/tower/util/struct.BoxCloneService.html), // but it doesn't fit (it's unconditionally `!Sync`, and `Sync` is required for `HttpClient<_>` to implement RPC traits). #[derive(Clone)] -pub struct L2Client { +pub struct Client { inner: C, url: SensitiveUrl, rate_limit: SharedRateLimit, component_name: &'static str, metrics: &'static L2ClientMetrics, + _network: PhantomData, } -impl fmt::Debug for L2Client { +/// Client using the WebSocket transport. +pub type WsClient = Client>; + +impl fmt::Debug for Client { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter - .debug_struct("L2Client") + .debug_struct("Client") // Do not expose the entire client `Debug` representation, which may (and in case of `HttpClient`, does) // include potentially sensitive URL and/or HTTP headers .field("inner", &any::type_name::()) @@ -129,15 +137,27 @@ impl fmt::Debug for L2Client { } } -impl L2Client { - /// Creates an HTTP-backed L2 client. - pub fn http(url: SensitiveUrl) -> anyhow::Result { +impl Client { + /// Creates an HTTP-backed client. + pub fn http(url: SensitiveUrl) -> anyhow::Result> { let client = HttpClientBuilder::default().build(url.expose_str())?; - Ok(L2ClientBuilder::new(client, url)) + Ok(ClientBuilder::new(client, url)) } } -impl L2Client { +impl WsClient { + /// Creates a WS-backed client. + pub async fn ws( + url: SensitiveUrl, + ) -> anyhow::Result>> { + let client = ws_client::WsClientBuilder::default() + .build(url.expose_str()) + .await?; + Ok(ClientBuilder::new(Shared::new(client), url)) + } +} + +impl Client { async fn limit_rate(&self, origin: CallOrigin<'_>) -> Result<(), Error> { const RATE_LIMIT_TIMEOUT: Duration = Duration::from_secs(10); @@ -189,7 +209,11 @@ impl L2Client { } } -impl TaggedClient for L2Client { +impl ForNetwork for Client { + type Net = Net; +} + +impl TaggedClient for Client { fn for_component(mut self, component_name: &'static str) -> Self { self.component_name = component_name; self @@ -197,7 +221,7 @@ impl TaggedClient for L2Client { } #[async_trait] -impl ClientT for L2Client { +impl ClientT for Client { async fn notification(&self, method: &str, params: Params) -> Result<(), Error> where Params: ToRpcParams + Send, @@ -231,7 +255,7 @@ impl ClientT for L2Client { } #[async_trait] -impl SubscriptionClientT for L2Client { +impl SubscriptionClientT for Client { async fn subscribe<'a, Notif, Params>( &self, subscribe_method: &'a str, @@ -264,14 +288,15 @@ impl SubscriptionClientT for L2Client { } } -/// Builder for the [`L2Client`]. -pub struct L2ClientBuilder { +/// Builder for the [`Client`]. +pub struct ClientBuilder { client: C, url: SensitiveUrl, rate_limit: (usize, Duration), + _network: PhantomData, } -impl fmt::Debug for L2ClientBuilder { +impl fmt::Debug for ClientBuilder { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("L2ClientBuilder") @@ -282,13 +307,14 @@ impl fmt::Debug for L2ClientBuilder { } } -impl L2ClientBuilder { +impl ClientBuilder { /// Wraps the provided client. fn new(client: C, url: SensitiveUrl) -> Self { Self { client, url, rate_limit: (1, Duration::ZERO), + _network: PhantomData, } } @@ -309,7 +335,7 @@ impl L2ClientBuilder { } /// Builds the client. - pub fn build(self) -> L2Client { + pub fn build(self) -> Client { tracing::info!( "Creating JSON-RPC client with inner client: {:?} and rate limit: {:?}", self.client, @@ -318,12 +344,13 @@ impl L2ClientBuilder { let rate_limit = SharedRateLimit::new(self.rate_limit.0, self.rate_limit.1); METRICS.observe_config(&rate_limit); - L2Client { + Client { inner: self.client, url: self.url, rate_limit, component_name: "", metrics: &METRICS, + _network: PhantomData, } } } diff --git a/core/lib/web3_decl/src/client/network.rs b/core/lib/web3_decl/src/client/network.rs new file mode 100644 index 00000000000..6b1a1d82550 --- /dev/null +++ b/core/lib/web3_decl/src/client/network.rs @@ -0,0 +1,30 @@ +//! `Network` and related types. + +/// Marker trait for network types. Two standard networks are [`L1`] and [`L2`]. +pub trait Network: 'static + Copy + Sync + Send {} + +/// L1 (i.e., Ethereum) network. +#[derive(Debug, Clone, Copy)] +pub struct L1(()); + +impl Network for L1 {} + +/// L2 (i.e., zkSync Era) network. +#[derive(Debug, Clone, Copy)] +pub struct L2(()); + +impl Network for L2 {} + +/// Associates a type with a particular network. +pub trait ForNetwork { + /// Network that the type is associated with. + type Net: Network; +} + +impl ForNetwork for &T { + type Net = T::Net; +} + +impl ForNetwork for Box { + type Net = T::Net; +} diff --git a/core/lib/web3_decl/src/client/shared.rs b/core/lib/web3_decl/src/client/shared.rs new file mode 100644 index 00000000000..abab1de21ac --- /dev/null +++ b/core/lib/web3_decl/src/client/shared.rs @@ -0,0 +1,89 @@ +//! `Shared` RPC client. + +use std::{fmt, sync::Arc}; + +use async_trait::async_trait; +use jsonrpsee::core::{ + client::{BatchResponse, ClientT, Error, Subscription, SubscriptionClientT}, + params::BatchRequestBuilder, + traits::ToRpcParams, +}; +use serde::de::DeserializeOwned; + +#[derive(Debug)] +pub struct Shared(Arc); + +impl Clone for Shared { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Shared { + pub fn new(client: T) -> Self { + Self(Arc::new(client)) + } +} + +impl From> for Shared { + fn from(client_arc: Arc) -> Self { + Self(client_arc) + } +} + +#[async_trait] +impl ClientT for Shared { + async fn notification(&self, method: &str, params: Params) -> Result<(), Error> + where + Params: ToRpcParams + Send, + { + self.0.as_ref().notification(method, params).await + } + + async fn request(&self, method: &str, params: Params) -> Result + where + R: DeserializeOwned, + Params: ToRpcParams + Send, + { + self.0.as_ref().request(method, params).await + } + + async fn batch_request<'a, R>( + &self, + batch: BatchRequestBuilder<'a>, + ) -> Result, Error> + where + R: DeserializeOwned + fmt::Debug + 'a, + { + self.0.batch_request(batch).await + } +} + +#[async_trait] +impl SubscriptionClientT for Shared { + async fn subscribe<'a, Notif, Params>( + &self, + subscribe_method: &'a str, + params: Params, + unsubscribe_method: &'a str, + ) -> Result, Error> + where + Params: ToRpcParams + Send, + Notif: DeserializeOwned, + { + self.0 + .as_ref() + .subscribe(subscribe_method, params, unsubscribe_method) + .await + } + + async fn subscribe_to_method<'a, Notif>( + &self, + method: &'a str, + ) -> Result, Error> + where + Notif: DeserializeOwned, + { + self.0.as_ref().subscribe_to_method(method).await + } +} diff --git a/core/lib/web3_decl/src/client/tests.rs b/core/lib/web3_decl/src/client/tests.rs index bad14458a9c..6f6f0a56823 100644 --- a/core/lib/web3_decl/src/client/tests.rs +++ b/core/lib/web3_decl/src/client/tests.rs @@ -193,7 +193,7 @@ async fn rate_limiting_with_rng_and_threads(rate_limit: usize) { async fn wrapping_mock_client() { tokio::time::pause(); - let client = MockL2Client::new_async(|method, _| { + let client = MockClient::::new_async(|method, _| { Box::pin(async move { match method { "ok" => Ok(serde_json::from_value(serde_json::json!("ok"))?), @@ -215,7 +215,7 @@ async fn wrapping_mock_client() { }) }); - let mut client = L2ClientBuilder::new(client, "http://localhost".parse().unwrap()) + let mut client = ClientBuilder::::new(client, "http://localhost".parse().unwrap()) .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) .build() .for_component("test"); diff --git a/core/lib/web3_decl/src/namespaces/debug.rs b/core/lib/web3_decl/src/namespaces/debug.rs index c54903d7e3c..b5ee49b5eba 100644 --- a/core/lib/web3_decl/src/namespaces/debug.rs +++ b/core/lib/web3_decl/src/namespaces/debug.rs @@ -5,15 +5,18 @@ use zksync_types::{ transaction_request::CallRequest, }; -use crate::types::H256; +use crate::{ + client::{ForNetwork, L2}, + types::H256, +}; #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "debug") + rpc(server, client, namespace = "debug", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "debug") + rpc(client, namespace = "debug", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/web3_decl/src/namespaces/en.rs b/core/lib/web3_decl/src/namespaces/en.rs index 29a96f33a3d..65d31b6dcd9 100644 --- a/core/lib/web3_decl/src/namespaces/en.rs +++ b/core/lib/web3_decl/src/namespaces/en.rs @@ -2,13 +2,15 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use zksync_config::{configs::EcosystemContracts, GenesisConfig}; use zksync_types::{api::en, tokens::TokenInfo, Address, L2BlockNumber}; +use crate::client::{ForNetwork, L2}; + #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "en") + rpc(server, client, namespace = "en", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "en") + rpc(client, namespace = "en", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/web3_decl/src/namespaces/eth.rs b/core/lib/web3_decl/src/namespaces/eth.rs index 864a0c25375..a7701c3d872 100644 --- a/core/lib/web3_decl/src/namespaces/eth.rs +++ b/core/lib/web3_decl/src/namespaces/eth.rs @@ -8,18 +8,21 @@ use zksync_types::{ Address, H256, }; -use crate::types::{ - Block, Bytes, FeeHistory, Filter, FilterChanges, Index, Log, PubSubFilter, SyncState, - TransactionReceipt, U256, U64, +use crate::{ + client::{ForNetwork, L2}, + types::{ + Block, Bytes, FeeHistory, Filter, FilterChanges, Index, Log, PubSubFilter, SyncState, + TransactionReceipt, U256, U64, + }, }; #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "eth") + rpc(server, client, namespace = "eth", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "eth") + rpc(client, namespace = "eth", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/web3_decl/src/namespaces/eth_subscribe.rs b/core/lib/web3_decl/src/namespaces/eth_subscribe.rs deleted file mode 100644 index 8b137891791..00000000000 --- a/core/lib/web3_decl/src/namespaces/eth_subscribe.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/core/lib/web3_decl/src/namespaces/mod.rs b/core/lib/web3_decl/src/namespaces/mod.rs index e3fcc6669a7..bca5d29ccfe 100644 --- a/core/lib/web3_decl/src/namespaces/mod.rs +++ b/core/lib/web3_decl/src/namespaces/mod.rs @@ -1,12 +1,3 @@ -pub mod debug; -pub mod en; -pub mod eth; -pub mod eth_subscribe; -pub mod net; -pub mod snapshots; -pub mod web3; -pub mod zks; - #[cfg(feature = "client")] pub use self::{ debug::DebugNamespaceClient, en::EnNamespaceClient, eth::EthNamespaceClient, @@ -19,3 +10,11 @@ pub use self::{ eth::EthPubSubServer, net::NetNamespaceServer, snapshots::SnapshotsNamespaceClient, web3::Web3NamespaceServer, zks::ZksNamespaceServer, }; + +pub mod debug; +pub mod en; +pub mod eth; +pub mod net; +pub mod snapshots; +pub mod web3; +pub mod zks; diff --git a/core/lib/web3_decl/src/namespaces/net.rs b/core/lib/web3_decl/src/namespaces/net.rs index 9213b353671..3a3880f8de9 100644 --- a/core/lib/web3_decl/src/namespaces/net.rs +++ b/core/lib/web3_decl/src/namespaces/net.rs @@ -1,13 +1,15 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use zksync_types::U256; +use crate::client::{ForNetwork, L2}; + #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "net") + rpc(server, client, namespace = "net", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "net") + rpc(client, namespace = "net", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/web3_decl/src/namespaces/snapshots.rs b/core/lib/web3_decl/src/namespaces/snapshots.rs index 02f9aa6b36d..de9b89580d6 100644 --- a/core/lib/web3_decl/src/namespaces/snapshots.rs +++ b/core/lib/web3_decl/src/namespaces/snapshots.rs @@ -4,13 +4,15 @@ use zksync_types::{ L1BatchNumber, }; +use crate::client::{ForNetwork, L2}; + #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "snapshots") + rpc(server, client, namespace = "snapshots", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "snapshots") + rpc(client, namespace = "snapshots", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/web3_decl/src/namespaces/web3.rs b/core/lib/web3_decl/src/namespaces/web3.rs index b143477a5f1..c8d9d44ff68 100644 --- a/core/lib/web3_decl/src/namespaces/web3.rs +++ b/core/lib/web3_decl/src/namespaces/web3.rs @@ -1,12 +1,14 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use crate::client::{ForNetwork, L2}; + #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "web3") + rpc(server, client, namespace = "web3", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "web3") + rpc(client, namespace = "web3", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/web3_decl/src/namespaces/zks.rs b/core/lib/web3_decl/src/namespaces/zks.rs index 12f2c7fe594..0ae98a6130c 100644 --- a/core/lib/web3_decl/src/namespaces/zks.rs +++ b/core/lib/web3_decl/src/namespaces/zks.rs @@ -12,15 +12,18 @@ use zksync_types::{ Address, L1BatchNumber, L2BlockNumber, H256, U256, U64, }; -use crate::types::{Bytes, Token}; +use crate::{ + client::{ForNetwork, L2}, + types::{Bytes, Token}, +}; #[cfg_attr( all(feature = "client", feature = "server"), - rpc(server, client, namespace = "zks") + rpc(server, client, namespace = "zks", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(feature = "client", not(feature = "server")), - rpc(client, namespace = "zks") + rpc(client, namespace = "zks", client_bounds(Self: ForNetwork)) )] #[cfg_attr( all(not(feature = "client"), feature = "server"), diff --git a/core/lib/zksync_core/src/api_server/tx_sender/proxy.rs b/core/lib/zksync_core/src/api_server/tx_sender/proxy.rs index 9155cbfac77..37ba7755fd6 100644 --- a/core/lib/zksync_core/src/api_server/tx_sender/proxy.rs +++ b/core/lib/zksync_core/src/api_server/tx_sender/proxy.rs @@ -15,7 +15,7 @@ use zksync_types::{ Address, Nonce, H256, }; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, error::{ClientRpcContext, EnrichedClientResult, Web3Error}, namespaces::{EthNamespaceClient, ZksNamespaceClient}, }; @@ -109,11 +109,11 @@ impl TxCache { #[derive(Debug)] pub struct TxProxy { tx_cache: TxCache, - client: BoxedL2Client, + client: Box>, } impl TxProxy { - pub fn new(client: BoxedL2Client) -> Self { + pub fn new(client: Box>) -> Self { Self { client: client.for_component("tx_proxy"), tx_cache: TxCache::default(), diff --git a/core/lib/zksync_core/src/api_server/web3/tests/debug.rs b/core/lib/zksync_core/src/api_server/web3/tests/debug.rs index a025aff3085..a074c143057 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/debug.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/debug.rs @@ -1,7 +1,10 @@ //! Tests for the `debug` Web3 namespace. use zksync_types::{tx::TransactionExecutionResult, vm_trace::Call, BOOTLOADER_ADDRESS}; -use zksync_web3_decl::namespaces::DebugNamespaceClient; +use zksync_web3_decl::{ + client::{DynClient, L2}, + namespaces::DebugNamespaceClient, +}; use super::*; @@ -34,7 +37,11 @@ struct TraceBlockTest(L2BlockNumber); #[async_trait] impl HttpTest for TraceBlockTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let tx_results = [0, 1, 2].map(execute_l2_transaction_with_traces); let mut storage = pool.connection().await?; let new_l2_block = store_l2_block(&mut storage, self.0, &tx_results).await?; @@ -97,7 +104,11 @@ struct TraceBlockFlatTest(L2BlockNumber); #[async_trait] impl HttpTest for TraceBlockFlatTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let tx_results = [0, 1, 2].map(execute_l2_transaction_with_traces); let mut storage = pool.connection().await?; store_l2_block(&mut storage, self.0, &tx_results).await?; @@ -173,7 +184,11 @@ struct TraceTransactionTest; #[async_trait] impl HttpTest for TraceTransactionTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let tx_results = [execute_l2_transaction_with_traces(0)]; let mut storage = pool.connection().await?; store_l2_block(&mut storage, L2BlockNumber(1), &tx_results).await?; @@ -212,7 +227,11 @@ impl HttpTest for TraceBlockTestWithSnapshotRecovery { StorageInitialization::empty_recovery() } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let snapshot_l2_block_number = StorageInitialization::SNAPSHOT_RECOVERY_BLOCK; let missing_l2_block_numbers = [ L2BlockNumber(0), diff --git a/core/lib/zksync_core/src/api_server/web3/tests/filters.rs b/core/lib/zksync_core/src/api_server/web3/tests/filters.rs index 7fb38425310..4358e99be42 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/filters.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/filters.rs @@ -22,7 +22,11 @@ impl HttpTest for BasicFilterChangesTest { } } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let block_filter_id = client.new_block_filter().await?; let tx_filter_id = client.new_pending_transaction_filter().await?; @@ -104,7 +108,11 @@ impl HttpTest for LogFilterChangesTest { } } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let all_logs_filter_id = client.new_filter(Filter::default()).await?; let address_filter = Filter { address: Some(Address::repeat_byte(23).into()), @@ -175,7 +183,11 @@ struct LogFilterChangesWithBlockBoundariesTest; #[async_trait] impl HttpTest for LogFilterChangesWithBlockBoundariesTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let lower_bound_filter = Filter { from_block: Some(api::BlockNumber::Number(2.into())), ..Filter::default() @@ -279,7 +291,11 @@ struct DisableFiltersTest; #[async_trait] impl HttpTest for DisableFiltersTest { - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let filter = Filter { from_block: Some(api::BlockNumber::Number(2.into())), ..Filter::default() diff --git a/core/lib/zksync_core/src/api_server/web3/tests/mod.rs b/core/lib/zksync_core/src/api_server/web3/tests/mod.rs index 7f2751b3e0b..e8c38053612 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/mod.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/mod.rs @@ -48,6 +48,7 @@ use zksync_types::{ }; use zksync_utils::u256_to_h256; use zksync_web3_decl::{ + client::{Client, DynClient, L2}, jsonrpsee::{http_client::HttpClient, types::error::ErrorCode}, namespaces::{EnNamespaceClient, EthNamespaceClient, ZksNamespaceClient}, }; @@ -267,7 +268,8 @@ trait HttpTest: Send + Sync { Arc::default() } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()>; + async fn test(&self, client: &DynClient, pool: &ConnectionPool) + -> anyhow::Result<()>; /// Overrides the `filters_disabled` configuration parameter for HTTP server startup fn filters_disabled(&self) -> bool { @@ -360,9 +362,9 @@ async fn test_http_server(test: impl HttpTest) { .await; let local_addr = server_handles.wait_until_ready().await; - let client = ::builder() - .build(format!("http://{local_addr}/")) - .unwrap(); + let client = Client::::http(format!("http://{local_addr}/").parse().unwrap()) + .unwrap() + .build(); test.test(&client, &pool).await.unwrap(); stop_sender.send_replace(true); @@ -513,7 +515,11 @@ struct HttpServerBasicsTest; #[async_trait] impl HttpTest for HttpServerBasicsTest { - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let block_number = client.get_block_number().await?; assert_eq!(block_number, U64::from(0)); @@ -543,7 +549,11 @@ impl HttpTest for BlockMethodsWithSnapshotRecovery { StorageInitialization::empty_recovery() } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let block = client.get_block_by_number(1_000.into(), false).await?; assert!(block.is_none()); @@ -616,7 +626,11 @@ impl HttpTest for L1BatchMethodsWithSnapshotRecovery { StorageInitialization::empty_recovery() } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let l2_block_number = StorageInitialization::SNAPSHOT_RECOVERY_BLOCK + 1; let l1_batch_number = StorageInitialization::SNAPSHOT_RECOVERY_BATCH + 1; assert_eq!( @@ -707,7 +721,11 @@ impl HttpTest for StorageAccessWithSnapshotRecovery { StorageInitialization::Recovery { logs, factory_deps } } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let address = Address::repeat_byte(1); let first_local_l2_block = StorageInitialization::SNAPSHOT_RECOVERY_BLOCK + 1; for number in [0, 1, first_local_l2_block.0 - 1] { @@ -748,7 +766,11 @@ struct TransactionCountTest; #[async_trait] impl HttpTest for TransactionCountTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let test_address = Address::repeat_byte(11); let mut storage = pool.connection().await?; let mut l2_block_number = L2BlockNumber(0); @@ -846,7 +868,11 @@ impl HttpTest for TransactionCountAfterSnapshotRecoveryTest { } } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let test_address = Address::repeat_byte(11); let pending_count = client.get_transaction_count(test_address, None).await?; assert_eq!(pending_count, 3.into()); @@ -901,7 +927,11 @@ struct TransactionReceiptsTest; #[async_trait] impl HttpTest for TransactionReceiptsTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let mut storage = pool.connection().await?; let l2_block_number = L2BlockNumber(1); @@ -959,7 +989,11 @@ impl AllAccountBalancesTest { #[async_trait] impl HttpTest for AllAccountBalancesTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let balances = client.get_all_account_balances(Self::ADDRESS).await?; assert_eq!(balances, HashMap::new()); @@ -1028,7 +1062,11 @@ impl HttpTest for RpcCallsTracingTest { self.tracer.clone() } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let block_number = client.get_block_number().await?; assert_eq!(block_number, U64::from(0)); @@ -1067,10 +1105,13 @@ impl HttpTest for RpcCallsTracingTest { assert_eq!(calls[0].metadata.block_diff, None); // Check protocol-level errors. - client - .request::("eth_unknownMethod", jsonrpsee::rpc_params![]) - .await - .unwrap_err(); + ClientT::request::( + &client, + "eth_unknownMethod", + jsonrpsee::rpc_params![], + ) + .await + .unwrap_err(); let calls = self.tracer.recorded_calls().take(); assert_eq!(calls.len(), 1); @@ -1080,10 +1121,13 @@ impl HttpTest for RpcCallsTracingTest { ); assert!(!calls[0].metadata.has_app_error); - client - .request::("eth_getBlockByNumber", jsonrpsee::rpc_params![0]) - .await - .unwrap_err(); + ClientT::request::( + &client, + "eth_getBlockByNumber", + jsonrpsee::rpc_params![0], + ) + .await + .unwrap_err(); let calls = self.tracer.recorded_calls().take(); assert_eq!(calls.len(), 1); @@ -1094,13 +1138,13 @@ impl HttpTest for RpcCallsTracingTest { assert!(!calls[0].metadata.has_app_error); // Check app-level error. - client - .request::( - "eth_getFilterLogs", - jsonrpsee::rpc_params![U256::from(1)], - ) - .await - .unwrap_err(); + ClientT::request::( + &client, + "eth_getFilterLogs", + jsonrpsee::rpc_params![U256::from(1)], + ) + .await + .unwrap_err(); let calls = self.tracer.recorded_calls().take(); assert_eq!(calls.len(), 1); @@ -1114,7 +1158,7 @@ impl HttpTest for RpcCallsTracingTest { let mut batch = BatchRequestBuilder::new(); batch.insert("eth_blockNumber", jsonrpsee::rpc_params![])?; batch.insert("zks_L1BatchNumber", jsonrpsee::rpc_params![])?; - let response = client.batch_request::(batch).await?; + let response = ClientT::batch_request::(&client, batch).await?; for response_part in response { assert_eq!(response_part.unwrap(), U64::from(0)); } @@ -1141,7 +1185,11 @@ struct GenesisConfigTest; #[async_trait] impl HttpTest for GenesisConfigTest { - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { // It's enough to check that we fill all fields and deserialization is correct. // Mocking values is not suitable since they will always change client.genesis_config().await.unwrap(); diff --git a/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs b/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs index d8af509dd6b..46261c8c6fb 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/snapshots.rs @@ -28,7 +28,11 @@ impl SnapshotBasicsTest { #[async_trait] impl HttpTest for SnapshotBasicsTest { - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let mut storage = pool.connection().await.unwrap(); store_l2_block( &mut storage, diff --git a/core/lib/zksync_core/src/api_server/web3/tests/vm.rs b/core/lib/zksync_core/src/api_server/web3/tests/vm.rs index cde7a9be79c..72baa7e40d0 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/vm.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/vm.rs @@ -58,7 +58,11 @@ impl HttpTest for CallTest { Self::create_executor(L2BlockNumber(0)) } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let call_result = client.call(Self::call_request(b"pending"), None).await?; assert_eq!(call_result.0, b"output"); @@ -110,7 +114,11 @@ impl HttpTest for CallTestAfterSnapshotRecovery { CallTest::create_executor(first_local_l2_block) } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let call_result = client .call(CallTest::call_request(b"pending"), None) .await?; @@ -223,7 +231,11 @@ impl HttpTest for SendRawTransactionTest { tx_executor } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { if !self.snapshot_recovery { // Manually set sufficient balance for the transaction account. let mut storage = pool.connection().await?; @@ -335,7 +347,11 @@ impl HttpTest for SendTransactionWithDetailedOutputTest { tx_executor } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { // Manually set sufficient balance for the transaction account. let mut storage = pool.connection().await?; storage @@ -408,7 +424,11 @@ impl HttpTest for TraceCallTest { CallTest::create_executor(L2BlockNumber(0)) } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let call_request = CallTest::call_request(b"pending"); let call_result = client.trace_call(call_request.clone(), None, None).await?; Self::assert_debug_call(&call_request, &call_result); @@ -473,7 +493,11 @@ impl HttpTest for TraceCallTestAfterSnapshotRecovery { CallTest::create_executor(number) } - async fn test(&self, client: &HttpClient, _pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + _pool: &ConnectionPool, + ) -> anyhow::Result<()> { let call_request = CallTest::call_request(b"pending"); let call_result = client.trace_call(call_request.clone(), None, None).await?; TraceCallTest::assert_debug_call(&call_request, &call_result); @@ -559,7 +583,11 @@ impl HttpTest for EstimateGasTest { tx_executor } - async fn test(&self, client: &HttpClient, pool: &ConnectionPool) -> anyhow::Result<()> { + async fn test( + &self, + client: &DynClient, + pool: &ConnectionPool, + ) -> anyhow::Result<()> { let l2_transaction = create_l2_transaction(10, 100); for threshold in [10_000, 50_000, 100_000, 1_000_000] { self.gas_limit_threshold.store(threshold, Ordering::Relaxed); diff --git a/core/lib/zksync_core/src/api_server/web3/tests/ws.rs b/core/lib/zksync_core/src/api_server/web3/tests/ws.rs index 646fb96d478..97190a72e96 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/ws.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/ws.rs @@ -10,10 +10,10 @@ use zksync_config::configs::chain::NetworkConfig; use zksync_dal::ConnectionPool; use zksync_types::{api, Address, L1BatchNumber, H256, U64}; use zksync_web3_decl::{ + client::{WsClient, L2}, jsonrpsee::{ core::client::{Subscription, SubscriptionClientT}, rpc_params, - ws_client::{WsClient, WsClientBuilder}, }, namespaces::{EthNamespaceClient, ZksNamespaceClient}, types::{BlockHeader, PubSubFilter}, @@ -151,7 +151,7 @@ trait WsTest: Send + Sync { async fn test( &self, - client: &WsClient, + client: &WsClient, pool: &ConnectionPool, pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()>; @@ -185,10 +185,10 @@ async fn test_ws_server(test: impl WsTest) { .await; let local_addr = server_handles.wait_until_ready().await; - let client = WsClientBuilder::default() - .build(format!("ws://{local_addr}")) + let client = Client::ws(format!("ws://{local_addr}").parse().unwrap()) .await - .unwrap(); + .unwrap() + .build(); test.test(&client, &pool, pub_sub_events).await.unwrap(); stop_sender.send_replace(true); @@ -202,7 +202,7 @@ struct WsServerCanStartTest; impl WsTest for WsServerCanStartTest { async fn test( &self, - client: &WsClient, + client: &WsClient, _pool: &ConnectionPool, _pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -243,7 +243,7 @@ impl WsTest for BasicSubscriptionsTest { async fn test( &self, - client: &WsClient, + client: &WsClient, pool: &ConnectionPool, mut pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -331,7 +331,7 @@ struct LogSubscriptions { impl LogSubscriptions { async fn new( - client: &WsClient, + client: &WsClient, pub_sub_events: &mut mpsc::UnboundedReceiver, ) -> anyhow::Result { // Wait for the notifier to get initialized so that it doesn't skip notifications @@ -382,7 +382,7 @@ impl WsTest for LogSubscriptionsTest { async fn test( &self, - client: &WsClient, + client: &WsClient, pool: &ConnectionPool, mut pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -471,7 +471,7 @@ struct LogSubscriptionsWithNewBlockTest; impl WsTest for LogSubscriptionsWithNewBlockTest { async fn test( &self, - client: &WsClient, + client: &WsClient, pool: &ConnectionPool, mut pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -519,7 +519,7 @@ struct LogSubscriptionsWithManyBlocksTest; impl WsTest for LogSubscriptionsWithManyBlocksTest { async fn test( &self, - client: &WsClient, + client: &WsClient, pool: &ConnectionPool, mut pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -565,7 +565,7 @@ struct LogSubscriptionsWithDelayTest; impl WsTest for LogSubscriptionsWithDelayTest { async fn test( &self, - client: &WsClient, + client: &WsClient, pool: &ConnectionPool, mut pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -635,7 +635,7 @@ struct RateLimitingTest; impl WsTest for RateLimitingTest { async fn test( &self, - client: &WsClient, + client: &WsClient, _pool: &ConnectionPool, _pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { @@ -672,7 +672,7 @@ struct BatchGetsRateLimitedTest; impl WsTest for BatchGetsRateLimitedTest { async fn test( &self, - client: &WsClient, + client: &WsClient, _pool: &ConnectionPool, _pub_sub_events: mpsc::UnboundedReceiver, ) -> anyhow::Result<()> { diff --git a/core/lib/zksync_core/src/consensus/era.rs b/core/lib/zksync_core/src/consensus/era.rs index 9deb137a9a4..fc6910ad453 100644 --- a/core/lib/zksync_core/src/consensus/era.rs +++ b/core/lib/zksync_core/src/consensus/era.rs @@ -7,7 +7,7 @@ use zksync_concurrency::ctx; use zksync_config::configs::consensus::{ConsensusConfig, ConsensusSecrets}; use zksync_dal::{ConnectionPool, Core}; -use zksync_web3_decl::client::BoxedL2Client; +use zksync_web3_decl::client::{DynClient, L2}; use super::{config, fetcher::Fetcher, storage::Store}; use crate::sync_layer::{sync_action::ActionQueueSender, SyncState}; @@ -36,7 +36,7 @@ pub async fn run_fetcher( cfg: Option<(ConsensusConfig, ConsensusSecrets)>, pool: ConnectionPool, sync_state: SyncState, - main_node_client: BoxedL2Client, + main_node_client: Box>, actions: ActionQueueSender, ) -> anyhow::Result<()> { let fetcher = Fetcher { diff --git a/core/lib/zksync_core/src/consensus/fetcher.rs b/core/lib/zksync_core/src/consensus/fetcher.rs index 787be42dbcc..dcea154d04f 100644 --- a/core/lib/zksync_core/src/consensus/fetcher.rs +++ b/core/lib/zksync_core/src/consensus/fetcher.rs @@ -3,7 +3,7 @@ use zksync_concurrency::{ctx, error::Wrap as _, scope, time}; use zksync_consensus_executor as executor; use zksync_consensus_roles::validator; use zksync_types::L2BlockNumber; -use zksync_web3_decl::client::BoxedL2Client; +use zksync_web3_decl::client::{DynClient, L2}; use crate::{ consensus::{storage, Store}, @@ -18,7 +18,7 @@ pub type P2PConfig = executor::Config; pub struct Fetcher { pub store: Store, pub sync_state: SyncState, - pub client: BoxedL2Client, + pub client: Box>, } impl Fetcher { diff --git a/core/lib/zksync_core/src/consensus/testonly.rs b/core/lib/zksync_core/src/consensus/testonly.rs index f1996cb2bf9..b500020b23e 100644 --- a/core/lib/zksync_core/src/consensus/testonly.rs +++ b/core/lib/zksync_core/src/consensus/testonly.rs @@ -16,7 +16,7 @@ use zksync_types::{ ProtocolVersionId, H256, }; use zksync_web3_decl::{ - client::{BoxedL2Client, L2Client}, + client::{Client, DynClient, L2}, error::{EnrichedClientError, EnrichedClientResult}, }; @@ -278,21 +278,21 @@ impl StateKeeper { } /// Connects to the json RPC endpoint exposed by the state keeper. - pub async fn connect(&self, ctx: &ctx::Ctx) -> ctx::Result { + pub async fn connect(&self, ctx: &ctx::Ctx) -> ctx::Result>> { let addr = sync::wait_for(ctx, &mut self.addr.clone(), Option::is_some) .await? .unwrap(); - let client = L2Client::http(format!("http://{addr}/").parse().context("url")?) + let client = Client::::http(format!("http://{addr}/").parse().context("url")?) .context("json_rpc()")? .build(); - Ok(BoxedL2Client::new(client)) + Ok(Box::new(client)) } /// Runs the centralized fetcher. pub async fn run_centralized_fetcher( self, ctx: &ctx::Ctx, - client: BoxedL2Client, + client: Box>, ) -> anyhow::Result<()> { Fetcher { store: self.store, @@ -307,7 +307,7 @@ impl StateKeeper { pub async fn run_p2p_fetcher( self, ctx: &ctx::Ctx, - client: BoxedL2Client, + client: Box>, cfg: P2PConfig, ) -> anyhow::Result<()> { Fetcher { diff --git a/core/lib/zksync_core/src/reorg_detector/mod.rs b/core/lib/zksync_core/src/reorg_detector/mod.rs index 9561649c0c8..b753b5442ed 100644 --- a/core/lib/zksync_core/src/reorg_detector/mod.rs +++ b/core/lib/zksync_core/src/reorg_detector/mod.rs @@ -8,7 +8,7 @@ use zksync_health_check::{Health, HealthStatus, HealthUpdater, ReactiveHealthChe use zksync_shared_metrics::{CheckerComponent, EN_METRICS}; use zksync_types::{L1BatchNumber, L2BlockNumber, H256}; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, error::{ClientRpcContext, EnrichedClientError, EnrichedClientResult}, namespaces::{EthNamespaceClient, ZksNamespaceClient}, }; @@ -91,7 +91,7 @@ trait MainNodeClient: fmt::Debug + Send + Sync { } #[async_trait] -impl MainNodeClient for BoxedL2Client { +impl MainNodeClient for Box> { async fn sealed_l2_block_number(&self) -> EnrichedClientResult { let number = self .get_block_number() @@ -221,7 +221,7 @@ pub struct ReorgDetector { impl ReorgDetector { const DEFAULT_SLEEP_INTERVAL: Duration = Duration::from_secs(5); - pub fn new(client: BoxedL2Client, pool: ConnectionPool) -> Self { + pub fn new(client: Box>, pool: ConnectionPool) -> Self { let (health_check, health_updater) = ReactiveHealthCheck::new("reorg_detector"); Self { client: Box::new(client.for_component("reorg_detector")), diff --git a/core/lib/zksync_core/src/sync_layer/batch_status_updater/mod.rs b/core/lib/zksync_core/src/sync_layer/batch_status_updater/mod.rs index 34a970e4121..966d3ead07b 100644 --- a/core/lib/zksync_core/src/sync_layer/batch_status_updater/mod.rs +++ b/core/lib/zksync_core/src/sync_layer/batch_status_updater/mod.rs @@ -16,7 +16,7 @@ use zksync_types::{ aggregated_operations::AggregatedActionType, api, L1BatchNumber, L2BlockNumber, H256, }; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, error::{ClientRpcContext, EnrichedClientError, EnrichedClientResult}, namespaces::ZksNamespaceClient, }; @@ -87,7 +87,7 @@ trait MainNodeClient: fmt::Debug + Send + Sync { } #[async_trait] -impl MainNodeClient for BoxedL2Client { +impl MainNodeClient for Box> { async fn resolve_l1_batch_to_l2_block( &self, number: L1BatchNumber, @@ -255,7 +255,7 @@ pub struct BatchStatusUpdater { impl BatchStatusUpdater { const DEFAULT_SLEEP_INTERVAL: Duration = Duration::from_secs(5); - pub fn new(client: BoxedL2Client, pool: ConnectionPool) -> Self { + pub fn new(client: Box>, pool: ConnectionPool) -> Self { Self::from_parts( Box::new(client.for_component("batch_status_updater")), pool, diff --git a/core/lib/zksync_core/src/sync_layer/client.rs b/core/lib/zksync_core/src/sync_layer/client.rs index c998b934cf1..2c25a0cea8e 100644 --- a/core/lib/zksync_core/src/sync_layer/client.rs +++ b/core/lib/zksync_core/src/sync_layer/client.rs @@ -10,7 +10,7 @@ use zksync_types::{ get_code_key, Address, L2BlockNumber, ProtocolVersionId, H256, U64, }; use zksync_web3_decl::{ - client::BoxedL2Client, + client::{DynClient, L2}, error::{ClientRpcContext, EnrichedClientError, EnrichedClientResult}, namespaces::{EnNamespaceClient, EthNamespaceClient, ZksNamespaceClient}, }; @@ -47,7 +47,7 @@ pub trait MainNodeClient: 'static + Send + Sync + fmt::Debug { } #[async_trait] -impl MainNodeClient for BoxedL2Client { +impl MainNodeClient for Box> { async fn fetch_system_contract_by_hash( &self, hash: H256, diff --git a/core/lib/zksync_core/src/sync_layer/sync_state.rs b/core/lib/zksync_core/src/sync_layer/sync_state.rs index 68d5e4be51c..91f66a5d3e8 100644 --- a/core/lib/zksync_core/src/sync_layer/sync_state.rs +++ b/core/lib/zksync_core/src/sync_layer/sync_state.rs @@ -8,7 +8,10 @@ use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_health_check::{CheckHealth, Health, HealthStatus}; use zksync_shared_metrics::EN_METRICS; use zksync_types::L2BlockNumber; -use zksync_web3_decl::{client::BoxedL2Client, namespaces::EthNamespaceClient}; +use zksync_web3_decl::{ + client::{DynClient, L2}, + namespaces::EthNamespaceClient, +}; use crate::state_keeper::{io::IoCursor, updates::UpdatesManager, StateKeeperOutputHandler}; @@ -78,7 +81,7 @@ impl SyncState { pub async fn run_updater( self, connection_pool: ConnectionPool, - main_node_client: BoxedL2Client, + main_node_client: Box>, mut stop_receiver: watch::Receiver, ) -> anyhow::Result<()> { const UPDATE_INTERVAL: Duration = Duration::from_secs(10); diff --git a/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs b/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs index 9f8c19ad19f..259a5e3e3fe 100644 --- a/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs +++ b/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs @@ -6,7 +6,9 @@ use std::{ use tokio::sync::watch::Receiver; use zksync_types::fee_model::FeeParams; use zksync_web3_decl::{ - client::BoxedL2Client, error::ClientRpcContext, namespaces::ZksNamespaceClient, + client::{DynClient, L2}, + error::ClientRpcContext, + namespaces::ZksNamespaceClient, }; use crate::BatchFeeModelInputProvider; @@ -21,12 +23,12 @@ const SLEEP_INTERVAL: Duration = Duration::from_secs(5); /// since it relies on the configuration, which may change. #[derive(Debug)] pub struct MainNodeFeeParamsFetcher { - client: BoxedL2Client, + client: Box>, main_node_fee_params: RwLock, } impl MainNodeFeeParamsFetcher { - pub fn new(client: BoxedL2Client) -> Self { + pub fn new(client: Box>) -> Self { Self { client: client.for_component("fee_params_fetcher"), main_node_fee_params: RwLock::new(FeeParams::sensible_v1_default()), diff --git a/core/node/node_framework/src/implementations/layers/consensus.rs b/core/node/node_framework/src/implementations/layers/consensus.rs index 9a45da43e12..e9f199d878c 100644 --- a/core/node/node_framework/src/implementations/layers/consensus.rs +++ b/core/node/node_framework/src/implementations/layers/consensus.rs @@ -7,7 +7,7 @@ use zksync_core::{ }; use zksync_dal::{ConnectionPool, Core}; use zksync_types::L2ChainId; -use zksync_web3_decl::client::BoxedL2Client; +use zksync_web3_decl::client::{DynClient, L2}; use crate::{ implementations::resources::{ @@ -141,7 +141,7 @@ impl Task for MainNodeConsensusTask { pub struct FetcherTask { config: Option<(ConsensusConfig, ConsensusSecrets)>, pool: ConnectionPool, - main_node_client: BoxedL2Client, + main_node_client: Box>, sync_state: SyncState, action_queue_sender: ActionQueueSender, } diff --git a/core/node/node_framework/src/implementations/resources/main_node_client.rs b/core/node/node_framework/src/implementations/resources/main_node_client.rs index 50a3a9be276..903a6ce9b9b 100644 --- a/core/node/node_framework/src/implementations/resources/main_node_client.rs +++ b/core/node/node_framework/src/implementations/resources/main_node_client.rs @@ -1,9 +1,9 @@ -use zksync_web3_decl::client::BoxedL2Client; +use zksync_web3_decl::client::{DynClient, L2}; use crate::resource::Resource; #[derive(Debug, Clone)] -pub struct MainNodeClientResource(pub BoxedL2Client); +pub struct MainNodeClientResource(pub Box>); impl Resource for MainNodeClientResource { fn name() -> String { diff --git a/core/tests/loadnext/src/account/mod.rs b/core/tests/loadnext/src/account/mod.rs index 627ef04c1df..d5bd22dd684 100644 --- a/core/tests/loadnext/src/account/mod.rs +++ b/core/tests/loadnext/src/account/mod.rs @@ -8,7 +8,10 @@ use futures::{channel::mpsc, SinkExt}; use tokio::sync::RwLock; use zksync_contracts::test_contracts::LoadnextContractExecutionParams; use zksync_types::{api::TransactionReceipt, Address, Nonce, H256, U256, U64}; -use zksync_web3_decl::jsonrpsee::core::ClientError as CoreError; +use zksync_web3_decl::{ + client::{Client, L2}, + jsonrpsee::core::ClientError as CoreError, +}; use crate::{ account::tx_command_executor::SubmitResult, @@ -17,7 +20,7 @@ use crate::{ config::{LoadtestConfig, RequestLimiters}, constants::{MAX_L1_TRANSACTIONS, POLLING_INTERVAL}, report::{Report, ReportBuilder, ReportLabel}, - sdk::{error::ClientError, operations::SyncTransactionHandle, HttpClient}, + sdk::{error::ClientError, operations::SyncTransactionHandle}, utils::format_gwei, }; @@ -359,7 +362,7 @@ impl AccountLifespan { async fn submit( &mut self, modifier: IncorrectnessModifier, - send_result: Result, ClientError>, + send_result: Result>, ClientError>, ) -> Result { let expected_outcome = modifier.expected_outcome(); diff --git a/core/tests/loadnext/src/account_pool.rs b/core/tests/loadnext/src/account_pool.rs index 3fc96971ee9..bb46699a31a 100644 --- a/core/tests/loadnext/src/account_pool.rs +++ b/core/tests/loadnext/src/account_pool.rs @@ -6,19 +6,20 @@ use rand::Rng; use tokio::time::timeout; use zksync_eth_signer::PrivateKeySigner; use zksync_types::{Address, K256PrivateKey, L2ChainId, H256}; +use zksync_web3_decl::client::{Client, L2}; use crate::{ config::LoadtestConfig, corrupted_tx::CorruptedSigner, fs_utils::{loadnext_contract, TestContract}, rng::{LoadtestRng, Random}, - sdk::{signer::Signer, HttpClient, HttpClientBuilder, Wallet, ZksNamespaceClient}, + sdk::{signer::Signer, Wallet, ZksNamespaceClient}, }; /// An alias to [`zksync::Wallet`] with HTTP client. Wrapped in `Arc` since /// the client cannot be cloned due to limitations in jsonrpsee. -pub type SyncWallet = Arc>; -pub type CorruptedSyncWallet = Arc>; +pub type SyncWallet = Arc>>; +pub type CorruptedSyncWallet = Arc>>; /// Thread-safe pool of the addresses of accounts used in the loadtest. #[derive(Debug, Clone)] @@ -90,11 +91,16 @@ pub struct AccountPool { impl AccountPool { /// Generates all the required test accounts and prepares `Wallet` objects. pub async fn new(config: &LoadtestConfig) -> anyhow::Result { - let l2_chain_id = L2ChainId::try_from(config.l2_chain_id).unwrap(); + let l2_chain_id = L2ChainId::try_from(config.l2_chain_id) + .map_err(|err| anyhow::anyhow!("invalid L2 chain ID: {err}"))?; // Create a client for pinging the RPC. - let client = HttpClientBuilder::default() - .build(&config.l2_rpc_address) - .unwrap(); + let client = Client::::http( + config + .l2_rpc_address + .parse() + .context("invalid L2 RPC URL")?, + )? + .build(); // Perform a health check: check whether zkSync server is alive. let mut server_alive = false; for _ in 0usize..3 { diff --git a/core/tests/loadnext/src/sdk/wallet.rs b/core/tests/loadnext/src/sdk/wallet.rs index 9dc7eff3c79..8a99a1166c5 100644 --- a/core/tests/loadnext/src/sdk/wallet.rs +++ b/core/tests/loadnext/src/sdk/wallet.rs @@ -8,7 +8,7 @@ use zksync_types::{ Address, Eip712Domain, U256, }; use zksync_web3_decl::{ - jsonrpsee::http_client::{HttpClient, HttpClientBuilder}, + client::{Client, L2}, namespaces::{EthNamespaceClient, NetNamespaceClient, Web3NamespaceClient, ZksNamespaceClient}, }; @@ -26,7 +26,7 @@ pub struct Wallet { pub signer: Signer, } -impl Wallet +impl Wallet> where S: EthereumSigner, { @@ -38,8 +38,13 @@ where pub fn with_http_client( rpc_address: &str, signer: Signer, - ) -> Result, ClientError> { - let client = HttpClientBuilder::default().build(rpc_address)?; + ) -> Result>, ClientError> { + let rpc_address = rpc_address + .parse() + .map_err(|err| ClientError::NetworkError(format!("error parsing RPC url: {err}")))?; + let client = Client::http(rpc_address) + .map_err(|err| ClientError::NetworkError(err.to_string()))? + .build(); Ok(Wallet { provider: client, From fa74cff676e1adea1e690fed898381605221f7ca Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 8 May 2024 17:06:54 +0300 Subject: [PATCH 02/12] Replace `QueryClient` with `Client` --- Cargo.lock | 1 + core/bin/block_reverter/src/main.rs | 12 +- core/bin/external_node/src/main.rs | 9 +- core/bin/zksync_server/src/main.rs | 7 +- core/lib/eth_client/Cargo.toml | 1 + core/lib/eth_client/src/clients/http/decl.rs | 3 +- core/lib/eth_client/src/clients/http/mod.rs | 5 +- core/lib/eth_client/src/clients/http/query.rs | 107 ++++++------------ core/lib/eth_client/src/clients/mod.rs | 4 +- core/lib/eth_client/src/lib.rs | 2 +- core/lib/web3_decl/src/client/boxed.rs | 23 +++- core/lib/web3_decl/src/client/mock.rs | 4 + core/lib/web3_decl/src/client/mod.rs | 8 +- core/lib/web3_decl/src/client/network.rs | 7 ++ core/lib/zksync_core/src/lib.rs | 10 +- .../fee_model/src/l1_gas_price/singleton.rs | 7 +- core/node/genesis/src/lib.rs | 4 +- .../layers/query_eth_client.rs | 6 +- core/tests/loadnext/src/sdk/ethereum/mod.rs | 13 ++- 19 files changed, 120 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b89168b2ce..4db0fd7a6f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8498,6 +8498,7 @@ dependencies = [ "zksync_contracts", "zksync_eth_signer", "zksync_types", + "zksync_web3_decl", ] [[package]] diff --git a/core/bin/block_reverter/src/main.rs b/core/bin/block_reverter/src/main.rs index 5bd15def73a..9dd728b4e47 100644 --- a/core/bin/block_reverter/src/main.rs +++ b/core/bin/block_reverter/src/main.rs @@ -5,7 +5,7 @@ use clap::{Parser, Subcommand}; use tokio::io::{self, AsyncReadExt}; use zksync_block_reverter::{ eth_client::{ - clients::{PKSigningClient, QueryClient}, + clients::{Client, PKSigningClient, L1}, EthInterface, }, BlockReverter, BlockReverterEthConfig, NodeRole, @@ -129,8 +129,9 @@ async fn main() -> anyhow::Result<()> { json, operator_address, } => { - let eth_client = - QueryClient::new(eth_sender.web3_url.clone()).context("Ethereum client")?; + let eth_client = Client::::http(eth_sender.web3_url.clone()) + .context("Ethereum client")? + .build(); let suggested_values = block_reverter .suggested_values(ð_client, &config, operator_address) @@ -146,8 +147,9 @@ async fn main() -> anyhow::Result<()> { priority_fee_per_gas, nonce, } => { - let eth_client = - QueryClient::new(eth_sender.web3_url.clone()).context("Ethereum client")?; + let eth_client = Client::::http(eth_sender.web3_url.clone()) + .context("Ethereum client")? + .build(); #[allow(deprecated)] let reverter_private_key = eth_sender .sender diff --git a/core/bin/external_node/src/main.rs b/core/bin/external_node/src/main.rs index 6610491de1f..09addb5ff1e 100644 --- a/core/bin/external_node/src/main.rs +++ b/core/bin/external_node/src/main.rs @@ -43,7 +43,7 @@ use zksync_dal::{metrics::PostgresMetrics, ConnectionPool, Core, CoreDal}; use zksync_db_connection::{ connection_pool::ConnectionPoolBuilder, healthcheck::ConnectionPoolHealthCheck, }; -use zksync_eth_client::{clients::QueryClient, EthInterface}; +use zksync_eth_client::EthInterface; use zksync_eth_sender::l1_batch_commit_data_generator::{ L1BatchCommitDataGenerator, RollupModeL1BatchCommitDataGenerator, ValidiumModeL1BatchCommitDataGenerator, @@ -56,7 +56,7 @@ use zksync_storage::RocksDB; use zksync_types::L2ChainId; use zksync_utils::wait_for_tasks::ManagedTasks; use zksync_web3_decl::{ - client::{Client, DynClient, L2}, + client::{Client, DynClient, L1, L2}, jsonrpsee, namespaces::EnNamespaceClient, }; @@ -828,7 +828,10 @@ async fn main() -> anyhow::Result<()> { let main_node_client = Box::new(main_node_client) as Box>; let eth_client_url = &required_config.eth_client_url; - let eth_client = Box::new(QueryClient::new(eth_client_url.clone())?); + let eth_client = Client::::http(eth_client_url.clone()) + .context("failed creating JSON-RPC client for Ethereum")? + .build(); + let eth_client = Box::new(eth_client); let mut config = ExternalNodeConfig::new( required_config, diff --git a/core/bin/zksync_server/src/main.rs b/core/bin/zksync_server/src/main.rs index a2a3eba027b..1677879d07c 100644 --- a/core/bin/zksync_server/src/main.rs +++ b/core/bin/zksync_server/src/main.rs @@ -24,7 +24,7 @@ use zksync_core::{ Component, Components, }; use zksync_env_config::FromEnv; -use zksync_eth_client::clients::QueryClient; +use zksync_eth_client::clients::{Client, L1}; use zksync_storage::RocksDB; use zksync_utils::wait_for_tasks::ManagedTasks; @@ -181,8 +181,9 @@ async fn main() -> anyhow::Result<()> { if let Some(ecosystem_contracts) = &contracts_config.ecosystem_contracts { let eth_config = configs.eth.as_ref().context("eth config")?; - let query_client = - QueryClient::new(eth_config.web3_url.clone()).context("Ethereum client")?; + let query_client = Client::::http(eth_config.web3_url.clone()) + .context("Ethereum client")? + .build(); zksync_node_genesis::save_set_chain_id_tx( &query_client, contracts_config.diamond_proxy_addr, diff --git a/core/lib/eth_client/Cargo.toml b/core/lib/eth_client/Cargo.toml index 24041e77f83..783501d171a 100644 --- a/core/lib/eth_client/Cargo.toml +++ b/core/lib/eth_client/Cargo.toml @@ -15,6 +15,7 @@ zksync_types.workspace = true zksync_eth_signer.workspace = true zksync_config.workspace = true zksync_contracts.workspace = true +zksync_web3_decl.workspace = true thiserror.workspace = true async-trait.workspace = true diff --git a/core/lib/eth_client/src/clients/http/decl.rs b/core/lib/eth_client/src/clients/http/decl.rs index 1dbda9d2264..efc0d946f95 100644 --- a/core/lib/eth_client/src/clients/http/decl.rs +++ b/core/lib/eth_client/src/clients/http/decl.rs @@ -1,8 +1,9 @@ use jsonrpsee::proc_macros::rpc; use zksync_types::{web3, Address, H256, U256, U64}; +use zksync_web3_decl::client::{ForNetwork, L1}; /// Subset of the L1 `eth` namespace used by the L1 client. -#[rpc(client, namespace = "eth")] +#[rpc(client, namespace = "eth", client_bounds(Self: ForNetwork))] pub(super) trait L1EthNamespace { #[method(name = "chainId")] async fn chain_id(&self) -> RpcResult; diff --git a/core/lib/eth_client/src/clients/http/mod.rs b/core/lib/eth_client/src/clients/http/mod.rs index bfdf2129a5e..2d1ed244afd 100644 --- a/core/lib/eth_client/src/clients/http/mod.rs +++ b/core/lib/eth_client/src/clients/http/mod.rs @@ -4,10 +4,7 @@ use vise::{ Buckets, Counter, EncodeLabelSet, EncodeLabelValue, Family, Histogram, LabeledFamily, Metrics, }; -pub use self::{ - query::QueryClient, - signing::{PKSigningClient, SigningClient}, -}; +pub use self::signing::{PKSigningClient, SigningClient}; mod decl; mod query; diff --git a/core/lib/eth_client/src/clients/http/query.rs b/core/lib/eth_client/src/clients/http/query.rs index 89b793d463f..8609fe2b6f2 100644 --- a/core/lib/eth_client/src/clients/http/query.rs +++ b/core/lib/eth_client/src/clients/http/query.rs @@ -1,8 +1,9 @@ use std::fmt; use async_trait::async_trait; -use jsonrpsee::{core::ClientError, http_client::HttpClient}; -use zksync_types::{url::SensitiveUrl, web3, Address, L1ChainId, H256, U256, U64}; +use jsonrpsee::core::ClientError; +use zksync_types::{web3, Address, L1ChainId, H256, U256, U64}; +use zksync_web3_decl::client::{TaggedClient, L1}; use super::{decl::L1EthNamespaceClient, Method, COUNTERS, LATENCIES}; use crate::{ @@ -10,51 +11,24 @@ use crate::{ EthInterface, RawTransactionBytes, }; -/// An "anonymous" Ethereum client that can invoke read-only methods that aren't -/// tied to a particular account. -#[derive(Clone)] -pub struct QueryClient { - web3: HttpClient, - url: SensitiveUrl, - component: &'static str, -} - -impl fmt::Debug for QueryClient { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("QueryClient") - .field("url", &self.url) - .field("component", &self.component) - .finish() - } -} - -impl QueryClient { - /// Creates a new HTTP client. - pub fn new(url: SensitiveUrl) -> Result { - Ok(Self { - web3: ::builder().build(url.expose_str())?, - url, - component: "", - }) - } -} - +// FIXME: double boxing is annoying #[async_trait] -impl EthInterface for QueryClient { +impl EthInterface for T +where + T: TaggedClient + L1EthNamespaceClient + Clone + fmt::Debug + Send + Sync + 'static, +{ fn clone_boxed(&self) -> Box { Box::new(self.clone()) } - fn for_component(mut self: Box, component_name: &'static str) -> Box { - self.component = component_name; - self + fn for_component(self: Box, component_name: &'static str) -> Box { + Box::new(TaggedClient::for_component(*self, component_name)) } async fn fetch_chain_id(&self) -> Result { - COUNTERS.call[&(Method::ChainId, self.component)].inc(); + COUNTERS.call[&(Method::ChainId, self.component())].inc(); let latency = LATENCIES.direct[&Method::ChainId].start(); - let raw_chain_id = self.web3.chain_id().await?; + let raw_chain_id = self.chain_id().await?; latency.observe(); let chain_id = u64::try_from(raw_chain_id).map_err(|err| { Error::EthereumGateway(ClientError::Custom(format!("invalid chainId: {err}"))) @@ -67,35 +41,32 @@ impl EthInterface for QueryClient { account: Address, block: web3::BlockNumber, ) -> Result { - COUNTERS.call[&(Method::NonceAtForAccount, self.component)].inc(); + COUNTERS.call[&(Method::NonceAtForAccount, self.component())].inc(); let latency = LATENCIES.direct[&Method::NonceAtForAccount].start(); - let nonce = self - .web3 - .get_transaction_count(account, Some(block)) - .await?; + let nonce = self.get_transaction_count(account, Some(block)).await?; latency.observe(); Ok(nonce) } async fn block_number(&self) -> Result { - COUNTERS.call[&(Method::BlockNumber, self.component)].inc(); + COUNTERS.call[&(Method::BlockNumber, self.component())].inc(); let latency = LATENCIES.direct[&Method::BlockNumber].start(); - let block_number = self.web3.get_block_number().await?; + let block_number = self.get_block_number().await?; latency.observe(); Ok(block_number) } async fn get_gas_price(&self) -> Result { - COUNTERS.call[&(Method::GetGasPrice, self.component)].inc(); + COUNTERS.call[&(Method::GetGasPrice, self.component())].inc(); let latency = LATENCIES.direct[&Method::GetGasPrice].start(); - let network_gas_price = self.web3.gas_price().await?; + let network_gas_price = self.gas_price().await?; latency.observe(); Ok(network_gas_price) } async fn send_raw_tx(&self, tx: RawTransactionBytes) -> Result { let latency = LATENCIES.direct[&Method::SendRawTx].start(); - let tx = self.web3.send_raw_transaction(web3::Bytes(tx.0)).await?; + let tx = self.send_raw_transaction(web3::Bytes(tx.0)).await?; latency.observe(); Ok(tx) } @@ -107,7 +78,7 @@ impl EthInterface for QueryClient { ) -> Result, Error> { const MAX_REQUEST_CHUNK: usize = 1024; - COUNTERS.call[&(Method::BaseFeeHistory, self.component)].inc(); + COUNTERS.call[&(Method::BaseFeeHistory, self.component())].inc(); let latency = LATENCIES.direct[&Method::BaseFeeHistory].start(); let mut history = Vec::with_capacity(block_count); let from_block = upto_block.saturating_sub(block_count); @@ -120,7 +91,6 @@ impl EthInterface for QueryClient { let chunk_size = chunk_end - chunk_start; let fee_history = self - .web3 .fee_history( U64::from(chunk_size), web3::BlockNumber::from(chunk_end), @@ -135,11 +105,10 @@ impl EthInterface for QueryClient { } async fn get_pending_block_base_fee_per_gas(&self) -> Result { - COUNTERS.call[&(Method::PendingBlockBaseFee, self.component)].inc(); + COUNTERS.call[&(Method::PendingBlockBaseFee, self.component())].inc(); let latency = LATENCIES.direct[&Method::PendingBlockBaseFee].start(); let block = self - .web3 .get_block_by_number(web3::BlockNumber::Pending, false) .await?; let block = if let Some(block) = block { @@ -147,8 +116,7 @@ impl EthInterface for QueryClient { } else { // Fallback for local reth. Because of artificial nature of producing blocks in local reth setup // there may be no pending block - self.web3 - .get_block_by_number(web3::BlockNumber::Latest, false) + self.get_block_by_number(web3::BlockNumber::Latest, false) .await? .expect("Latest block always exists") }; @@ -159,7 +127,7 @@ impl EthInterface for QueryClient { } async fn get_tx_status(&self, hash: H256) -> Result, Error> { - COUNTERS.call[&(Method::GetTxStatus, self.component)].inc(); + COUNTERS.call[&(Method::GetTxStatus, self.component())].inc(); let latency = LATENCIES.direct[&Method::GetTxStatus].start(); let receipt = self.tx_receipt(hash).await?; @@ -182,8 +150,8 @@ impl EthInterface for QueryClient { async fn failure_reason(&self, tx_hash: H256) -> Result, Error> { let latency = LATENCIES.direct[&Method::FailureReason].start(); - let transaction = self.web3.get_transaction_by_hash(tx_hash).await?; - let receipt = self.web3.get_transaction_receipt(tx_hash).await?; + let transaction = self.get_transaction_by_hash(tx_hash).await?; + let receipt = self.get_transaction_receipt(tx_hash).await?; match (transaction, receipt) { (Some(transaction), Some(receipt)) => { @@ -204,7 +172,6 @@ impl EthInterface for QueryClient { }; let err = self - .web3 .call(call_request, receipt.block_number.map(Into::into)) .await .err(); @@ -235,8 +202,8 @@ impl EthInterface for QueryClient { } async fn get_tx(&self, hash: H256) -> Result, Error> { - COUNTERS.call[&(Method::GetTx, self.component)].inc(); - let tx = self.web3.get_transaction_by_hash(hash).await?; + COUNTERS.call[&(Method::GetTx, self.component())].inc(); + let tx = self.get_transaction_by_hash(hash).await?; Ok(tx) } @@ -246,41 +213,41 @@ impl EthInterface for QueryClient { block: Option, ) -> Result { let latency = LATENCIES.direct[&Method::CallContractFunction].start(); - let output_bytes = self.web3.call(request, block).await?; + let output_bytes = self.call(request, block).await?; latency.observe(); Ok(output_bytes) } async fn tx_receipt(&self, tx_hash: H256) -> Result, Error> { - COUNTERS.call[&(Method::TxReceipt, self.component)].inc(); + COUNTERS.call[&(Method::TxReceipt, self.component())].inc(); let latency = LATENCIES.direct[&Method::TxReceipt].start(); - let receipt = self.web3.get_transaction_receipt(tx_hash).await?; + let receipt = self.get_transaction_receipt(tx_hash).await?; latency.observe(); Ok(receipt) } async fn eth_balance(&self, address: Address) -> Result { - COUNTERS.call[&(Method::EthBalance, self.component)].inc(); + COUNTERS.call[&(Method::EthBalance, self.component())].inc(); let latency = LATENCIES.direct[&Method::EthBalance].start(); - let balance = self.web3.get_balance(address, None).await?; + let balance = self.get_balance(address, None).await?; latency.observe(); Ok(balance) } async fn logs(&self, filter: web3::Filter) -> Result, Error> { - COUNTERS.call[&(Method::Logs, self.component)].inc(); + COUNTERS.call[&(Method::Logs, self.component())].inc(); let latency = LATENCIES.direct[&Method::Logs].start(); - let logs = self.web3.get_logs(filter).await?; + let logs = self.get_logs(filter).await?; latency.observe(); Ok(logs) } async fn block(&self, block_id: web3::BlockId) -> Result>, Error> { - COUNTERS.call[&(Method::Block, self.component)].inc(); + COUNTERS.call[&(Method::Block, self.component())].inc(); let latency = LATENCIES.direct[&Method::Block].start(); let block = match block_id { - web3::BlockId::Hash(hash) => self.web3.get_block_by_hash(hash, false).await?, - web3::BlockId::Number(num) => self.web3.get_block_by_number(num, false).await?, + web3::BlockId::Hash(hash) => self.get_block_by_hash(hash, false).await?, + web3::BlockId::Number(num) => self.get_block_by_number(num, false).await?, }; latency.observe(); Ok(block) diff --git a/core/lib/eth_client/src/clients/mod.rs b/core/lib/eth_client/src/clients/mod.rs index b2e31cc9aac..dee8ca23b5f 100644 --- a/core/lib/eth_client/src/clients/mod.rs +++ b/core/lib/eth_client/src/clients/mod.rs @@ -3,7 +3,9 @@ mod http; mod mock; +pub use zksync_web3_decl::client::{Client, L1}; + pub use self::{ - http::{PKSigningClient, QueryClient, SigningClient}, + http::{PKSigningClient, SigningClient}, mock::MockEthereum, }; diff --git a/core/lib/eth_client/src/lib.rs b/core/lib/eth_client/src/lib.rs index ef023592d14..313f5f2ba97 100644 --- a/core/lib/eth_client/src/lib.rs +++ b/core/lib/eth_client/src/lib.rs @@ -1,7 +1,6 @@ use std::fmt; use async_trait::async_trait; -pub use jsonrpsee::core::ClientError; use zksync_types::{ eth_sender::EthTxBlobSidecar, ethabi, web3, @@ -11,6 +10,7 @@ use zksync_types::{ }, Address, L1ChainId, H160, H256, U256, U64, }; +pub use zksync_web3_decl::jsonrpsee::core::ClientError; pub use crate::types::{ encode_blob_tx_with_sidecar, CallFunctionArgs, ContractCall, ContractError, Error, diff --git a/core/lib/web3_decl/src/client/boxed.rs b/core/lib/web3_decl/src/client/boxed.rs index ba091fa99f3..972ab2d6151 100644 --- a/core/lib/web3_decl/src/client/boxed.rs +++ b/core/lib/web3_decl/src/client/boxed.rs @@ -9,12 +9,7 @@ use jsonrpsee::core::{ }; use serde::de::DeserializeOwned; -use super::{ForNetwork, Network}; - -pub trait TaggedClient: ForNetwork { - /// Sets the component operating this client. This is used in logging etc. - fn for_component(self, component_name: &'static str) -> Self; -} +use super::{ForNetwork, Network, TaggedClient}; #[derive(Debug)] pub struct RawParams(Option>); @@ -44,6 +39,8 @@ pub trait ObjectSafeClient: 'static + Send + Sync + fmt::Debug + ForNetwork { component_name: &'static str, ) -> Box>; + fn component(&self) -> &'static str; + async fn notification(&self, method: &str, params: RawParams) -> Result<(), Error>; async fn request(&self, method: &str, params: RawParams) -> Result; @@ -70,6 +67,10 @@ where Box::new(TaggedClient::for_component(*self, component_name)) } + fn component(&self) -> &'static str { + TaggedClient::component(self) + } + async fn notification(&self, method: &str, params: RawParams) -> Result<(), Error> { ::notification(self, method, params).await } @@ -172,6 +173,16 @@ impl ClientT for Box> { } } +impl TaggedClient for Box> { + fn component(&self) -> &'static str { + (**self).component() + } + + fn for_component(self, component_name: &'static str) -> Self { + ObjectSafeClient::for_component(self, component_name) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/lib/web3_decl/src/client/mock.rs b/core/lib/web3_decl/src/client/mock.rs index f2de14ac8cc..74837c0f0fa 100644 --- a/core/lib/web3_decl/src/client/mock.rs +++ b/core/lib/web3_decl/src/client/mock.rs @@ -71,6 +71,10 @@ impl TaggedClient for MockClient { self.component_name = component_name; self } + + fn component(&self) -> &'static str { + self.component_name + } } #[async_trait] diff --git a/core/lib/web3_decl/src/client/mod.rs b/core/lib/web3_decl/src/client/mod.rs index 2f78a1e4012..2d566d8eac9 100644 --- a/core/lib/web3_decl/src/client/mod.rs +++ b/core/lib/web3_decl/src/client/mod.rs @@ -37,9 +37,9 @@ use zksync_types::url::SensitiveUrl; use self::metrics::{L2ClientMetrics, METRICS}; pub use self::{ - boxed::{DynClient, TaggedClient}, + boxed::DynClient, mock::MockClient, - network::{ForNetwork, Network, L1, L2}, + network::{ForNetwork, Network, TaggedClient, L1, L2}, shared::Shared, }; @@ -218,6 +218,10 @@ impl TaggedClient for Client { self.component_name = component_name; self } + + fn component(&self) -> &'static str { + self.component_name + } } #[async_trait] diff --git a/core/lib/web3_decl/src/client/network.rs b/core/lib/web3_decl/src/client/network.rs index 6b1a1d82550..0e1c3fa4beb 100644 --- a/core/lib/web3_decl/src/client/network.rs +++ b/core/lib/web3_decl/src/client/network.rs @@ -28,3 +28,10 @@ impl ForNetwork for &T { impl ForNetwork for Box { type Net = T::Net; } + +pub trait TaggedClient: ForNetwork { + /// Sets the component operating this client. This is used in logging etc. + fn for_component(self, component_name: &'static str) -> Self; + + fn component(&self) -> &'static str; +} diff --git a/core/lib/zksync_core/src/lib.rs b/core/lib/zksync_core/src/lib.rs index cc8976d6b22..37b0fdc2ec3 100644 --- a/core/lib/zksync_core/src/lib.rs +++ b/core/lib/zksync_core/src/lib.rs @@ -43,10 +43,7 @@ use zksync_config::{ use zksync_contracts::governance_contract; use zksync_dal::{metrics::PostgresMetrics, ConnectionPool, Core, CoreDal}; use zksync_db_connection::healthcheck::ConnectionPoolHealthCheck; -use zksync_eth_client::{ - clients::{PKSigningClient, QueryClient}, - BoundEthInterface, EthInterface, -}; +use zksync_eth_client::{clients::PKSigningClient, BoundEthInterface, EthInterface}; use zksync_eth_sender::{ l1_batch_commit_data_generator::{ L1BatchCommitDataGenerator, RollupModeL1BatchCommitDataGenerator, @@ -80,6 +77,7 @@ use zksync_queued_job_processor::JobProcessor; use zksync_shared_metrics::{InitStage, APP_METRICS}; use zksync_state::{PostgresStorageCaches, RocksdbStorageOptions}; use zksync_types::{ethabi::Contract, fee_model::FeeModelConfig, Address, L2ChainId}; +use zksync_web3_decl::client::{Client, L1}; use crate::{ api_server::{ @@ -302,7 +300,9 @@ pub async fn initialize_components( panic!("Circuit breaker triggered: {}", err); }); - let query_client = QueryClient::new(eth.web3_url.clone()).context("Ethereum client")?; + let query_client = Client::::http(eth.web3_url.clone()) + .context("Ethereum client")? + .build(); let query_client = Box::new(query_client); let gas_adjuster_config = eth.gas_adjuster.context("gas_adjuster")?; let sender = eth.sender.as_ref().context("sender")?; diff --git a/core/node/fee_model/src/l1_gas_price/singleton.rs b/core/node/fee_model/src/l1_gas_price/singleton.rs index 00a6b29ea84..b9ac88858ba 100644 --- a/core/node/fee_model/src/l1_gas_price/singleton.rs +++ b/core/node/fee_model/src/l1_gas_price/singleton.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use anyhow::Context as _; use tokio::{sync::watch, task::JoinHandle}; use zksync_config::{configs::eth_sender::PubdataSendingMode, GasAdjusterConfig}; -use zksync_eth_client::clients::QueryClient; use zksync_types::url::SensitiveUrl; +use zksync_web3_decl::client::{Client, L1}; use super::PubdataPricing; use crate::l1_gas_price::GasAdjuster; @@ -40,8 +40,9 @@ impl GasAdjusterSingleton { if let Some(adjuster) = &self.singleton { Ok(adjuster.clone()) } else { - let query_client = - QueryClient::new(self.web3_url.clone()).context("QueryClient::new()")?; + let query_client = Client::::http(self.web3_url.clone()) + .context("QueryClient::new()")? + .build(); let adjuster = GasAdjuster::new( Box::new(query_client), self.gas_adjuster_config, diff --git a/core/node/genesis/src/lib.rs b/core/node/genesis/src/lib.rs index fc95141adee..d2571cb8fe2 100644 --- a/core/node/genesis/src/lib.rs +++ b/core/node/genesis/src/lib.rs @@ -9,7 +9,7 @@ use multivm::utils::get_max_gas_per_pubdata_byte; use zksync_config::{GenesisConfig, PostgresConfig}; use zksync_contracts::{BaseSystemContracts, BaseSystemContractsHashes, SET_CHAIN_ID_EVENT}; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal, DalError}; -use zksync_eth_client::{clients::QueryClient, EthInterface}; +use zksync_eth_client::EthInterface; use zksync_merkle_tree::{domain::ZkSyncTree, TreeInstruction}; use zksync_system_constants::PRIORITY_EXPIRATION; use zksync_types::{ @@ -404,7 +404,7 @@ pub async fn create_genesis_l1_batch( // Save chain id transaction into the database // We keep returning anyhow and will refactor it later pub async fn save_set_chain_id_tx( - query_client: &QueryClient, + query_client: &dyn EthInterface, diamond_proxy_address: Address, state_transition_manager_address: Address, postgres_config: &PostgresConfig, diff --git a/core/node/node_framework/src/implementations/layers/query_eth_client.rs b/core/node/node_framework/src/implementations/layers/query_eth_client.rs index f7b3164695e..decee0140e3 100644 --- a/core/node/node_framework/src/implementations/layers/query_eth_client.rs +++ b/core/node/node_framework/src/implementations/layers/query_eth_client.rs @@ -1,6 +1,6 @@ use anyhow::Context; -use zksync_eth_client::clients::QueryClient; use zksync_types::url::SensitiveUrl; +use zksync_web3_decl::client::{Client, L1}; use crate::{ implementations::resources::eth_interface::EthInterfaceResource, @@ -26,7 +26,9 @@ impl WiringLayer for QueryEthClientLayer { } async fn wire(self: Box, mut context: ServiceContext<'_>) -> Result<(), WiringError> { - let query_client = QueryClient::new(self.web3_url.clone()).context("QueryClient::new()")?; + let query_client = Client::::http(self.web3_url.clone()) + .context("Client::new()")? + .build(); context.insert_resource(EthInterfaceResource(Box::new(query_client)))?; Ok(()) } diff --git a/core/tests/loadnext/src/sdk/ethereum/mod.rs b/core/tests/loadnext/src/sdk/ethereum/mod.rs index d4dabdeb519..0a81e79facf 100644 --- a/core/tests/loadnext/src/sdk/ethereum/mod.rs +++ b/core/tests/loadnext/src/sdk/ethereum/mod.rs @@ -4,8 +4,7 @@ use std::time::{Duration, Instant}; use serde_json::{Map, Value}; use zksync_eth_client::{ - clients::{QueryClient, SigningClient}, - BoundEthInterface, CallFunctionArgs, Error, EthInterface, Options, + clients::SigningClient, BoundEthInterface, CallFunctionArgs, Error, EthInterface, Options, }; use zksync_eth_signer::EthereumSigner; use zksync_types::{ @@ -17,7 +16,10 @@ use zksync_types::{ web3::{contract::Tokenize, TransactionReceipt}, Address, L1ChainId, L1TxCommonData, H160, H256, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, }; -use zksync_web3_decl::namespaces::{EthNamespaceClient, ZksNamespaceClient}; +use zksync_web3_decl::{ + client::{Client, L1}, + namespaces::{EthNamespaceClient, ZksNamespaceClient}, +}; use crate::sdk::{ error::ClientError, @@ -95,8 +97,9 @@ impl EthereumProvider { .as_ref() .parse::() .map_err(|err| ClientError::NetworkError(err.to_string()))?; - let query_client = QueryClient::new(eth_web3_url) - .map_err(|err| ClientError::NetworkError(err.to_string()))?; + let query_client = Client::::http(eth_web3_url) + .map_err(|err| ClientError::NetworkError(err.to_string()))? + .build(); let eth_client = SigningClient::new( Box::new(query_client).for_component("provider"), hyperchain_contract(), From f463f4e4722e146755ccc8ed5db55bbb38501e38 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 8 May 2024 17:37:05 +0300 Subject: [PATCH 03/12] Enrich errors for `EthInterface` methods --- core/lib/eth_client/src/clients/http/query.rs | 145 +++++++++++++----- core/lib/eth_client/src/clients/mock.rs | 4 +- core/lib/eth_client/src/lib.rs | 2 +- core/lib/eth_client/src/types.rs | 3 +- .../src/consistency_checker/mod.rs | 6 +- core/lib/zksync_core/src/utils/mod.rs | 5 +- core/node/eth_sender/src/eth_tx_manager.rs | 8 +- core/node/eth_watch/src/client.rs | 16 +- 8 files changed, 134 insertions(+), 55 deletions(-) diff --git a/core/lib/eth_client/src/clients/http/query.rs b/core/lib/eth_client/src/clients/http/query.rs index 8609fe2b6f2..e6acec4b0d3 100644 --- a/core/lib/eth_client/src/clients/http/query.rs +++ b/core/lib/eth_client/src/clients/http/query.rs @@ -3,7 +3,10 @@ use std::fmt; use async_trait::async_trait; use jsonrpsee::core::ClientError; use zksync_types::{web3, Address, L1ChainId, H256, U256, U64}; -use zksync_web3_decl::client::{TaggedClient, L1}; +use zksync_web3_decl::{ + client::{TaggedClient, L1}, + error::{ClientRpcContext, EnrichedClientError}, +}; use super::{decl::L1EthNamespaceClient, Method, COUNTERS, LATENCIES}; use crate::{ @@ -11,7 +14,6 @@ use crate::{ EthInterface, RawTransactionBytes, }; -// FIXME: double boxing is annoying #[async_trait] impl EthInterface for T where @@ -28,10 +30,12 @@ where async fn fetch_chain_id(&self) -> Result { COUNTERS.call[&(Method::ChainId, self.component())].inc(); let latency = LATENCIES.direct[&Method::ChainId].start(); - let raw_chain_id = self.chain_id().await?; + let raw_chain_id = self.chain_id().rpc_context("chain_id").await?; latency.observe(); let chain_id = u64::try_from(raw_chain_id).map_err(|err| { - Error::EthereumGateway(ClientError::Custom(format!("invalid chainId: {err}"))) + let err = ClientError::Custom(format!("invalid chainId: {err}")); + let err = EnrichedClientError::new(err, "chain_id").with_arg("chain_id", &raw_chain_id); + Error::EthereumGateway(err) })?; Ok(L1ChainId(chain_id)) } @@ -43,7 +47,12 @@ where ) -> Result { COUNTERS.call[&(Method::NonceAtForAccount, self.component())].inc(); let latency = LATENCIES.direct[&Method::NonceAtForAccount].start(); - let nonce = self.get_transaction_count(account, Some(block)).await?; + let nonce = self + .get_transaction_count(account, Some(block)) + .rpc_context("get_transaction_count") + .with_arg("account", &account) + .with_arg("block", &block) + .await?; latency.observe(); Ok(nonce) } @@ -51,7 +60,10 @@ where async fn block_number(&self) -> Result { COUNTERS.call[&(Method::BlockNumber, self.component())].inc(); let latency = LATENCIES.direct[&Method::BlockNumber].start(); - let block_number = self.get_block_number().await?; + let block_number = self + .get_block_number() + .rpc_context("get_block_number") + .await?; latency.observe(); Ok(block_number) } @@ -59,14 +71,17 @@ where async fn get_gas_price(&self) -> Result { COUNTERS.call[&(Method::GetGasPrice, self.component())].inc(); let latency = LATENCIES.direct[&Method::GetGasPrice].start(); - let network_gas_price = self.gas_price().await?; + let network_gas_price = self.gas_price().rpc_context("gas_price").await?; latency.observe(); Ok(network_gas_price) } async fn send_raw_tx(&self, tx: RawTransactionBytes) -> Result { let latency = LATENCIES.direct[&Method::SendRawTx].start(); - let tx = self.send_raw_transaction(web3::Bytes(tx.0)).await?; + let tx = self + .send_raw_transaction(web3::Bytes(tx.0)) + .rpc_context("send_raw_transaction") + .await?; latency.observe(); Ok(tx) } @@ -96,6 +111,9 @@ where web3::BlockNumber::from(chunk_end), None, ) + .rpc_context("fee_history") + .with_arg("chunk_size", &chunk_size) + .with_arg("block", &chunk_end) .await?; history.extend(fee_history.base_fee_per_gas); } @@ -110,6 +128,9 @@ where let block = self .get_block_by_number(web3::BlockNumber::Pending, false) + .rpc_context("get_block_by_number") + .with_arg("number", &web3::BlockNumber::Pending) + .with_arg("with_transactions", &false) .await?; let block = if let Some(block) = block { block @@ -117,6 +138,9 @@ where // Fallback for local reth. Because of artificial nature of producing blocks in local reth setup // there may be no pending block self.get_block_by_number(web3::BlockNumber::Latest, false) + .rpc_context("get_block_by_number") + .with_arg("number", &web3::BlockNumber::Latest) + .with_arg("with_transactions", &false) .await? .expect("Latest block always exists") }; @@ -150,8 +174,16 @@ where async fn failure_reason(&self, tx_hash: H256) -> Result, Error> { let latency = LATENCIES.direct[&Method::FailureReason].start(); - let transaction = self.get_transaction_by_hash(tx_hash).await?; - let receipt = self.get_transaction_receipt(tx_hash).await?; + let transaction = self + .get_transaction_by_hash(tx_hash) + .rpc_context("failure_reason#get_transaction_by_hash") + .with_arg("hash", &tx_hash) + .await?; + let receipt = self + .get_transaction_receipt(tx_hash) + .rpc_context("failure_reason#get_transaction_receipt") + .with_arg("hash", &tx_hash) + .await?; match (transaction, receipt) { (Some(transaction), Some(receipt)) => { @@ -171,27 +203,33 @@ where access_list: None, }; - let err = self - .call(call_request, receipt.block_number.map(Into::into)) - .await - .err(); - - let failure_info = match err { - Some(ClientError::Call(call_err)) => { - let revert_code = call_err.code().into(); - let message_len = - "execution reverted: ".len().min(call_err.message().len()); - let revert_reason = call_err.message()[message_len..].to_string(); - - Ok(Some(FailureInfo { - revert_code, - revert_reason, - gas_used, - gas_limit, - })) + let block_number = receipt.block_number.map(Into::into); + let result = self + .call(call_request.clone(), block_number) + .rpc_context("failure_reason#call") + .with_arg("call_request", &call_request) + .with_arg("block_number", &block_number) + .await; + + let failure_info = match result { + Err(err) => { + if let ClientError::Call(call_err) = err.as_ref() { + let revert_code = call_err.code().into(); + let message_len = + "execution reverted: ".len().min(call_err.message().len()); + let revert_reason = call_err.message()[message_len..].to_string(); + + Ok(Some(FailureInfo { + revert_code, + revert_reason, + gas_used, + gas_limit, + })) + } else { + Err(err.into()) + } } - Some(err) => Err(err.into()), - None => Ok(None), + Ok(_) => Ok(None), }; latency.observe(); @@ -203,7 +241,11 @@ where async fn get_tx(&self, hash: H256) -> Result, Error> { COUNTERS.call[&(Method::GetTx, self.component())].inc(); - let tx = self.get_transaction_by_hash(hash).await?; + let tx = self + .get_transaction_by_hash(hash) + .rpc_context("get_transaction_by_hash") + .with_arg("hash", &hash) + .await?; Ok(tx) } @@ -213,7 +255,12 @@ where block: Option, ) -> Result { let latency = LATENCIES.direct[&Method::CallContractFunction].start(); - let output_bytes = self.call(request, block).await?; + let output_bytes = self + .call(request.clone(), block) + .rpc_context("call") + .with_arg("request", &request) + .with_arg("block", &block) + .await?; latency.observe(); Ok(output_bytes) } @@ -221,7 +268,11 @@ where async fn tx_receipt(&self, tx_hash: H256) -> Result, Error> { COUNTERS.call[&(Method::TxReceipt, self.component())].inc(); let latency = LATENCIES.direct[&Method::TxReceipt].start(); - let receipt = self.get_transaction_receipt(tx_hash).await?; + let receipt = self + .get_transaction_receipt(tx_hash) + .rpc_context("get_transaction_receipt") + .with_arg("hash", &tx_hash) + .await?; latency.observe(); Ok(receipt) } @@ -229,7 +280,11 @@ where async fn eth_balance(&self, address: Address) -> Result { COUNTERS.call[&(Method::EthBalance, self.component())].inc(); let latency = LATENCIES.direct[&Method::EthBalance].start(); - let balance = self.get_balance(address, None).await?; + let balance = self + .get_balance(address, None) + .rpc_context("get_balance") + .with_arg("address", &address) + .await?; latency.observe(); Ok(balance) } @@ -237,7 +292,11 @@ where async fn logs(&self, filter: web3::Filter) -> Result, Error> { COUNTERS.call[&(Method::Logs, self.component())].inc(); let latency = LATENCIES.direct[&Method::Logs].start(); - let logs = self.get_logs(filter).await?; + let logs = self + .get_logs(filter.clone()) + .rpc_context("get_logs") + .with_arg("filter", &filter) + .await?; latency.observe(); Ok(logs) } @@ -246,8 +305,20 @@ where COUNTERS.call[&(Method::Block, self.component())].inc(); let latency = LATENCIES.direct[&Method::Block].start(); let block = match block_id { - web3::BlockId::Hash(hash) => self.get_block_by_hash(hash, false).await?, - web3::BlockId::Number(num) => self.get_block_by_number(num, false).await?, + web3::BlockId::Hash(hash) => { + self.get_block_by_hash(hash, false) + .rpc_context("get_block_by_hash") + .with_arg("hash", &hash) + .with_arg("with_transactions", &false) + .await? + } + web3::BlockId::Number(num) => { + self.get_block_by_number(num, false) + .rpc_context("get_block_by_number") + .with_arg("number", &num) + .with_arg("with_transactions", &false) + .await? + } }; latency.observe(); Ok(block) diff --git a/core/lib/eth_client/src/clients/mock.rs b/core/lib/eth_client/src/clients/mock.rs index 60d14bc29f0..698932a0947 100644 --- a/core/lib/eth_client/src/clients/mock.rs +++ b/core/lib/eth_client/src/clients/mock.rs @@ -11,6 +11,7 @@ use zksync_types::{ web3::{self, contract::Tokenize, BlockId}, Address, L1ChainId, H160, H256, U256, U64, }; +use zksync_web3_decl::error::EnrichedClientError; use crate::{ types::{Error, ExecutedTxStatus, FailureInfo, SignedCallResult}, @@ -327,7 +328,8 @@ impl EthInterface for MockEthereum { "transaction with the same nonce already processed", None::<()>, ); - return Err(Error::EthereumGateway(ClientError::Call(err))); + let err = EnrichedClientError::new(ClientError::Call(err), "send_raw_transaction"); + return Err(Error::EthereumGateway(err)); } if mock_tx.nonce == inner.pending_nonce { diff --git a/core/lib/eth_client/src/lib.rs b/core/lib/eth_client/src/lib.rs index 313f5f2ba97..054ed279e84 100644 --- a/core/lib/eth_client/src/lib.rs +++ b/core/lib/eth_client/src/lib.rs @@ -10,7 +10,7 @@ use zksync_types::{ }, Address, L1ChainId, H160, H256, U256, U64, }; -pub use zksync_web3_decl::jsonrpsee::core::ClientError; +pub use zksync_web3_decl::{error::EnrichedClientError, jsonrpsee::core::ClientError}; pub use crate::types::{ encode_blob_tx_with_sidecar, CallFunctionArgs, ContractCall, ContractError, Error, diff --git a/core/lib/eth_client/src/types.rs b/core/lib/eth_client/src/types.rs index c30a8540def..173cbc119e4 100644 --- a/core/lib/eth_client/src/types.rs +++ b/core/lib/eth_client/src/types.rs @@ -8,6 +8,7 @@ use zksync_types::{ }, Address, EIP_4844_TX_TYPE, H256, U256, }; +use zksync_web3_decl::error::EnrichedClientError; use crate::EthInterface; @@ -159,7 +160,7 @@ pub enum ContractError { pub enum Error { /// Problem on the Ethereum client side (e.g. bad RPC call, network issues). #[error("Request to ethereum gateway failed: {0}")] - EthereumGateway(#[from] jsonrpsee::core::ClientError), + EthereumGateway(#[from] EnrichedClientError), /// Problem with a contract call. #[error("Call to contract failed: {0}")] Contract(#[from] ContractError), diff --git a/core/lib/zksync_core/src/consistency_checker/mod.rs b/core/lib/zksync_core/src/consistency_checker/mod.rs index 9b94a9c5ef6..fe09525463c 100644 --- a/core/lib/zksync_core/src/consistency_checker/mod.rs +++ b/core/lib/zksync_core/src/consistency_checker/mod.rs @@ -5,7 +5,7 @@ use serde::Serialize; use tokio::sync::watch; use zksync_contracts::PRE_BOOJUM_COMMIT_FUNCTION; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; -use zksync_eth_client::{CallFunctionArgs, ClientError, Error as L1ClientError, EthInterface}; +use zksync_eth_client::{CallFunctionArgs, Error as L1ClientError, EthInterface}; use zksync_eth_sender::l1_batch_commit_data_generator::L1BatchCommitDataGenerator; use zksync_health_check::{Health, HealthStatus, HealthUpdater, ReactiveHealthCheck}; use zksync_l1_contract_interface::i_executor::commit::kzg::ZK_SYNC_BYTES_PER_BLOB; @@ -36,9 +36,7 @@ impl CheckError { fn is_transient(&self) -> bool { matches!( self, - Self::Web3(L1ClientError::EthereumGateway( - ClientError::Transport(_) | ClientError::RequestTimeout - )) + Self::Web3(L1ClientError::EthereumGateway(err)) if err.is_transient() ) } } diff --git a/core/lib/zksync_core/src/utils/mod.rs b/core/lib/zksync_core/src/utils/mod.rs index 2235792fce7..29ed89cf4ef 100644 --- a/core/lib/zksync_core/src/utils/mod.rs +++ b/core/lib/zksync_core/src/utils/mod.rs @@ -207,6 +207,7 @@ mod tests { use zksync_eth_client::{clients::MockEthereum, ClientError}; use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; use zksync_types::{ethabi, U256}; + use zksync_web3_decl::error::EnrichedClientError; use super::*; @@ -274,8 +275,8 @@ mod tests { } fn mock_ethereum_with_transport_error() -> MockEthereum { - let err = - EthClientError::EthereumGateway(ClientError::Transport(anyhow::anyhow!("unreachable"))); + let err = ClientError::Transport(anyhow::anyhow!("unreachable")); + let err = EthClientError::EthereumGateway(EnrichedClientError::new(err, "call")); mock_ethereum(ethabi::Token::Uint(U256::zero()), Some(err)) } diff --git a/core/node/eth_sender/src/eth_tx_manager.rs b/core/node/eth_sender/src/eth_tx_manager.rs index 5570d3bd6fa..b51315ab7dd 100644 --- a/core/node/eth_sender/src/eth_tx_manager.rs +++ b/core/node/eth_sender/src/eth_tx_manager.rs @@ -5,8 +5,8 @@ use tokio::sync::watch; use zksync_config::configs::eth_sender::SenderConfig; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; use zksync_eth_client::{ - encode_blob_tx_with_sidecar, BoundEthInterface, ClientError, Error, EthInterface, - ExecutedTxStatus, Options, RawTransactionBytes, SignedCallResult, + encode_blob_tx_with_sidecar, BoundEthInterface, ClientError, EnrichedClientError, Error, + EthInterface, ExecutedTxStatus, Options, RawTransactionBytes, SignedCallResult, }; use zksync_node_fee_model::l1_gas_price::L1TxParamsProvider; use zksync_shared_metrics::BlockL1Stage; @@ -223,6 +223,10 @@ impl EthTxManager { next_block_minimal_base_fee ); let err = ClientError::Custom("base_fee_per_gas is too low".into()); + let err = EnrichedClientError::new(err, "increase_priority_fee") + .with_arg("base_fee_per_gas", &base_fee_per_gas) + .with_arg("previous_base_fee", &previous_base_fee) + .with_arg("next_block_minimal_base_fee", &next_block_minimal_base_fee); return Err(ETHSenderError::from(Error::EthereumGateway(err))); } diff --git a/core/node/eth_watch/src/client.rs b/core/node/eth_watch/src/client.rs index f617b1f1826..177a538194a 100644 --- a/core/node/eth_watch/src/client.rs +++ b/core/node/eth_watch/src/client.rs @@ -2,7 +2,7 @@ use std::fmt; use zksync_contracts::verifier_contract; pub(super) use zksync_eth_client::Error as EthClientError; -use zksync_eth_client::{CallFunctionArgs, ClientError, EthInterface}; +use zksync_eth_client::{CallFunctionArgs, ClientError, EnrichedClientError, EthInterface}; use zksync_types::{ ethabi::Contract, web3::{BlockId, BlockNumber, FilterBuilder, Log}, @@ -115,8 +115,8 @@ impl EthClient for EthHttpQueryClient { // Note: we don't handle rate-limits here - assumption is that we're never going to hit them. if let Err(EthClientError::EthereumGateway(err)) = &result { tracing::warn!("Provider returned error message: {:?}", err); - let err_message = err.to_string(); - let err_code = if let ClientError::Call(err) = err { + let err_message = err.as_ref().to_string(); + let err_code = if let ClientError::Call(err) = err.as_ref() { Some(err.code()) } else { None @@ -188,11 +188,13 @@ impl EthClient for EthHttpQueryClient { .block(BlockId::Number(BlockNumber::Finalized)) .await? .ok_or_else(|| { - ClientError::Custom("Finalized block must be present on L1".into()) + let err = ClientError::Custom("Finalized block must be present on L1".into()); + EnrichedClientError::new(err, "block") })?; - let block_number = block - .number - .ok_or_else(|| ClientError::Custom("Finalized block must contain number".into()))?; + let block_number = block.number.ok_or_else(|| { + let err = ClientError::Custom("Finalized block must contain number".into()); + EnrichedClientError::new(err, "block").with_arg("block", &block) + })?; Ok(block_number.as_u64()) } } From 82d43675b15efeba36b433b76759ffe6962df6dc Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 8 May 2024 18:46:41 +0300 Subject: [PATCH 04/12] Sketch network metric labels --- core/bin/block_reverter/src/main.rs | 5 +- core/bin/external_node/src/main.rs | 5 +- core/bin/zksync_server/src/main.rs | 2 +- core/lib/web3_decl/src/client/boxed.rs | 8 +-- core/lib/web3_decl/src/client/metrics.rs | 45 ++++++++++++---- core/lib/web3_decl/src/client/mod.rs | 52 ++++++++++++------- core/lib/web3_decl/src/client/network.rs | 38 ++++++++++---- core/lib/web3_decl/src/client/tests.rs | 17 ++++-- .../src/api_server/web3/tests/mod.rs | 9 ++-- .../src/api_server/web3/tests/ws.rs | 2 +- .../lib/zksync_core/src/consensus/testonly.rs | 9 ++-- core/lib/zksync_core/src/lib.rs | 3 +- core/node/eth_watch/src/client.rs | 2 +- .../fee_model/src/l1_gas_price/singleton.rs | 7 ++- .../node/node_framework/examples/main_node.rs | 6 ++- .../layers/query_eth_client.rs | 9 ++-- core/tests/loadnext/src/account_pool.rs | 3 +- core/tests/loadnext/src/sdk/ethereum/mod.rs | 5 +- core/tests/loadnext/src/sdk/wallet.rs | 2 +- 19 files changed, 159 insertions(+), 70 deletions(-) diff --git a/core/bin/block_reverter/src/main.rs b/core/bin/block_reverter/src/main.rs index 9dd728b4e47..29349f94804 100644 --- a/core/bin/block_reverter/src/main.rs +++ b/core/bin/block_reverter/src/main.rs @@ -129,7 +129,8 @@ async fn main() -> anyhow::Result<()> { json, operator_address, } => { - let eth_client = Client::::http(eth_sender.web3_url.clone()) + // FIXME: doesn't work + let eth_client = Client::http(L1(0.into()), eth_sender.web3_url.clone()) .context("Ethereum client")? .build(); @@ -147,7 +148,7 @@ async fn main() -> anyhow::Result<()> { priority_fee_per_gas, nonce, } => { - let eth_client = Client::::http(eth_sender.web3_url.clone()) + let eth_client = Client::http(L1(0.into()), eth_sender.web3_url.clone()) .context("Ethereum client")? .build(); #[allow(deprecated)] diff --git a/core/bin/external_node/src/main.rs b/core/bin/external_node/src/main.rs index 09addb5ff1e..a2e2b986c4f 100644 --- a/core/bin/external_node/src/main.rs +++ b/core/bin/external_node/src/main.rs @@ -821,14 +821,15 @@ async fn main() -> anyhow::Result<()> { // Build L1 and L2 clients. let main_node_url = &required_config.main_node_url; tracing::info!("Main node URL is: {main_node_url:?}"); - let main_node_client = Client::::http(main_node_url.clone()) + // FIXME: merge + let main_node_client = Client::http(L2(0.into()), main_node_url.clone()) .context("Failed creating JSON-RPC client for main node")? .with_allowed_requests_per_second(optional_config.main_node_rate_limit_rps) .build(); let main_node_client = Box::new(main_node_client) as Box>; let eth_client_url = &required_config.eth_client_url; - let eth_client = Client::::http(eth_client_url.clone()) + let eth_client = Client::http(L1(0.into()), eth_client_url.clone()) .context("failed creating JSON-RPC client for Ethereum")? .build(); let eth_client = Box::new(eth_client); diff --git a/core/bin/zksync_server/src/main.rs b/core/bin/zksync_server/src/main.rs index 1677879d07c..80ec1d225ad 100644 --- a/core/bin/zksync_server/src/main.rs +++ b/core/bin/zksync_server/src/main.rs @@ -181,7 +181,7 @@ async fn main() -> anyhow::Result<()> { if let Some(ecosystem_contracts) = &contracts_config.ecosystem_contracts { let eth_config = configs.eth.as_ref().context("eth config")?; - let query_client = Client::::http(eth_config.web3_url.clone()) + let query_client = Client::http(L1(genesis.l1_chain_id), eth_config.web3_url.clone()) .context("Ethereum client")? .build(); zksync_node_genesis::save_set_chain_id_tx( diff --git a/core/lib/web3_decl/src/client/boxed.rs b/core/lib/web3_decl/src/client/boxed.rs index 972ab2d6151..8691d60275c 100644 --- a/core/lib/web3_decl/src/client/boxed.rs +++ b/core/lib/web3_decl/src/client/boxed.rs @@ -174,13 +174,13 @@ impl ClientT for Box> { } impl TaggedClient for Box> { - fn component(&self) -> &'static str { - (**self).component() - } - fn for_component(self, component_name: &'static str) -> Self { ObjectSafeClient::for_component(self, component_name) } + + fn component(&self) -> &'static str { + (**self).component() + } } #[cfg(test)] diff --git a/core/lib/web3_decl/src/client/metrics.rs b/core/lib/web3_decl/src/client/metrics.rs index 5bc2a2153b3..be07e68c8a2 100644 --- a/core/lib/web3_decl/src/client/metrics.rs +++ b/core/lib/web3_decl/src/client/metrics.rs @@ -5,19 +5,21 @@ use std::time::Duration; use jsonrpsee::{core::client, http_client::transport}; use vise::{ Buckets, Counter, DurationAsSecs, EncodeLabelSet, EncodeLabelValue, Family, Histogram, Info, - Metrics, Unit, + LabeledFamily, Metrics, Unit, }; use super::{AcquireStats, CallOrigin, SharedRateLimit}; #[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] pub(super) struct RequestLabels { + pub network: String, pub component: &'static str, pub method: String, } #[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] pub(super) struct RpcErrorLabels { + pub network: String, pub component: &'static str, pub method: String, pub code: i32, @@ -25,6 +27,7 @@ pub(super) struct RpcErrorLabels { #[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] pub(super) struct HttpErrorLabels { + pub network: String, pub component: &'static str, pub method: String, pub status: Option, @@ -40,6 +43,7 @@ pub(super) enum CallErrorKind { #[derive(Debug, Clone, PartialEq, Eq, Hash, EncodeLabelSet)] pub(super) struct GenericErrorLabels { + network: String, component: &'static str, method: String, kind: CallErrorKind, @@ -56,7 +60,8 @@ struct L2ClientConfigLabels { #[metrics(prefix = "l2_client")] pub(super) struct L2ClientMetrics { /// Client configuration. - info: Info, + #[metrics(labels = ["network"])] + info: LabeledFamily>, /// Number of requests timed out in the rate-limiting logic. pub rate_limit_timeout: Family, /// Latency of rate-limiting logic for rate-limited requests. @@ -71,28 +76,31 @@ pub(super) struct L2ClientMetrics { } impl L2ClientMetrics { - pub fn observe_config(&self, rate_limit: &SharedRateLimit) { + pub fn observe_config(&self, network: String, rate_limit: &SharedRateLimit) { let config_labels = L2ClientConfigLabels { rate_limit: rate_limit.rate_limit, rate_limit_window: rate_limit.rate_limit_window.into(), }; - if let Err(err) = self.info.set(config_labels) { + let info = &self.info[&network]; + if let Err(err) = info.set(config_labels) { tracing::warn!( "Error setting configuration info {:?} for L2 client; already set to {:?}", err.into_inner(), - self.info.get() + info.get() ); } } pub fn observe_rate_limit_latency( &self, + network: &str, component: &'static str, origin: CallOrigin<'_>, stats: &AcquireStats, ) { for method in origin.distinct_method_names() { let request_labels = RequestLabels { + network: network.to_owned(), component, method: method.to_owned(), }; @@ -100,9 +108,15 @@ impl L2ClientMetrics { } } - pub fn observe_rate_limit_timeout(&self, component: &'static str, origin: CallOrigin<'_>) { + pub fn observe_rate_limit_timeout( + &self, + network: &str, + component: &'static str, + origin: CallOrigin<'_>, + ) { for method in origin.distinct_method_names() { let request_labels = RequestLabels { + network: network.to_owned(), component, method: method.to_owned(), }; @@ -112,25 +126,34 @@ impl L2ClientMetrics { pub fn observe_error( &self, + network: &str, component: &'static str, origin: CallOrigin<'_>, err: &client::Error, ) { for method in origin.distinct_method_names() { - self.observe_error_inner(component, method, err); + self.observe_error_inner(network.to_owned(), component, method, err); } } - fn observe_error_inner(&self, component: &'static str, method: &str, err: &client::Error) { + fn observe_error_inner( + &self, + network: String, + component: &'static str, + method: &str, + err: &client::Error, + ) { let kind = match err { client::Error::Call(err) => { let labels = RpcErrorLabels { + network, component, method: method.to_owned(), code: err.code(), }; if self.rpc_errors[&labels].inc() == 0 { tracing::warn!( + network = labels.network, component, method, code = err.code(), @@ -148,12 +171,14 @@ impl L2ClientMetrics { _ => None, }); let labels = HttpErrorLabels { + network, component, method: method.to_owned(), status, }; if self.http_errors[&labels].inc() == 0 { tracing::warn!( + network = labels.network, component, method, status, @@ -168,15 +193,17 @@ impl L2ClientMetrics { }; let labels = GenericErrorLabels { + network, component, method: method.to_owned(), kind, }; if self.generic_errors[&labels].inc() == 0 { tracing::warn!( + network = labels.network, component, method, - "Request `{method}` from component `{component}` failed with generic error: {err}" + "Request `{method}` from component `{component}` failed with generic error: {err}", ); } } diff --git a/core/lib/web3_decl/src/client/mod.rs b/core/lib/web3_decl/src/client/mod.rs index 2d566d8eac9..b9368401ffd 100644 --- a/core/lib/web3_decl/src/client/mod.rs +++ b/core/lib/web3_decl/src/client/mod.rs @@ -15,7 +15,6 @@ use std::{ any, collections::HashSet, fmt, - marker::PhantomData, num::NonZeroUsize, sync::{Arc, Mutex}, time::Duration, @@ -117,13 +116,13 @@ pub struct Client { rate_limit: SharedRateLimit, component_name: &'static str, metrics: &'static L2ClientMetrics, - _network: PhantomData, + network: Net, } /// Client using the WebSocket transport. pub type WsClient = Client>; -impl fmt::Debug for Client { +impl fmt::Debug for Client { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("Client") @@ -133,27 +132,29 @@ impl fmt::Debug for Client { .field("url", &self.url) .field("rate_limit", &self.rate_limit) .field("component_name", &self.component_name) + .field("network", &self.network) .finish_non_exhaustive() } } impl Client { /// Creates an HTTP-backed client. - pub fn http(url: SensitiveUrl) -> anyhow::Result> { + pub fn http(network: Net, url: SensitiveUrl) -> anyhow::Result> { let client = HttpClientBuilder::default().build(url.expose_str())?; - Ok(ClientBuilder::new(client, url)) + Ok(ClientBuilder::new(client, network, url)) } } impl WsClient { /// Creates a WS-backed client. pub async fn ws( + network: Net, url: SensitiveUrl, ) -> anyhow::Result>> { let client = ws_client::WsClientBuilder::default() .build(url.expose_str()) .await?; - Ok(ClientBuilder::new(Shared::new(client), url)) + Ok(ClientBuilder::new(Shared::new(client), network, url)) } } @@ -166,11 +167,17 @@ impl Client { self.rate_limit.acquire(origin.request_count()), ) .await; + + let network_label = self.network.metric_label(); let stats = match rate_limit_result { Err(_) => { - self.metrics - .observe_rate_limit_timeout(self.component_name, origin); + self.metrics.observe_rate_limit_timeout( + &network_label, + self.component_name, + origin, + ); tracing::warn!( + network = network_label, component = self.component_name, %origin, "Request to {origin} by component `{}` timed out during rate limiting using policy {} reqs/{:?}", @@ -184,9 +191,14 @@ impl Client { Ok(stats) => stats, }; - self.metrics - .observe_rate_limit_latency(self.component_name, origin, &stats); + self.metrics.observe_rate_limit_latency( + &network_label, + self.component_name, + origin, + &stats, + ); tracing::warn!( + network = network_label, component = self.component_name, %origin, "Request to {origin} by component `{}` was rate-limited using policy {} reqs/{:?}: {stats:?}", @@ -203,7 +215,9 @@ impl Client { call_result: Result, ) -> Result { if let Err(err) = &call_result { - self.metrics.observe_error(self.component_name, origin, err); + let network_label = self.network.metric_label(); + self.metrics + .observe_error(&network_label, self.component_name, origin, err); } call_result } @@ -297,28 +311,29 @@ pub struct ClientBuilder { client: C, url: SensitiveUrl, rate_limit: (usize, Duration), - _network: PhantomData, + network: Net, } -impl fmt::Debug for ClientBuilder { +impl fmt::Debug for ClientBuilder { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("L2ClientBuilder") .field("client", &any::type_name::()) .field("url", &self.url) .field("rate_limit", &self.rate_limit) + .field("network", &self.network) .finish_non_exhaustive() } } impl ClientBuilder { /// Wraps the provided client. - fn new(client: C, url: SensitiveUrl) -> Self { + fn new(client: C, network: Net, url: SensitiveUrl) -> Self { Self { client, url, rate_limit: (1, Duration::ZERO), - _network: PhantomData, + network, } } @@ -341,12 +356,13 @@ impl ClientBuilder { /// Builds the client. pub fn build(self) -> Client { tracing::info!( - "Creating JSON-RPC client with inner client: {:?} and rate limit: {:?}", + "Creating JSON-RPC client for network {:?} with inner client: {:?} and rate limit: {:?}", + self.network, self.client, self.rate_limit ); let rate_limit = SharedRateLimit::new(self.rate_limit.0, self.rate_limit.1); - METRICS.observe_config(&rate_limit); + METRICS.observe_config(self.network.metric_label(), &rate_limit); Client { inner: self.client, @@ -354,7 +370,7 @@ impl ClientBuilder { rate_limit, component_name: "", metrics: &METRICS, - _network: PhantomData, + network: self.network, } } } diff --git a/core/lib/web3_decl/src/client/network.rs b/core/lib/web3_decl/src/client/network.rs index 0e1c3fa4beb..b4dcd847995 100644 --- a/core/lib/web3_decl/src/client/network.rs +++ b/core/lib/web3_decl/src/client/network.rs @@ -1,21 +1,38 @@ //! `Network` and related types. -/// Marker trait for network types. Two standard networks are [`L1`] and [`L2`]. -pub trait Network: 'static + Copy + Sync + Send {} +use std::fmt; + +use zksync_types::{L1ChainId, L2ChainId}; + +/// Marker trait for networks. Two standard network kinds are [`L1`] and [`L2`]. +/// +/// The `Display` implementation will be used in logging. +pub trait Network: 'static + Copy + Sync + Send + fmt::Debug { + fn metric_label(&self) -> String; +} /// L1 (i.e., Ethereum) network. #[derive(Debug, Clone, Copy)] -pub struct L1(()); +pub struct L1(pub L1ChainId); -impl Network for L1 {} +impl Network for L1 { + fn metric_label(&self) -> String { + format!("ethereum_{}", self.0) + } +} /// L2 (i.e., zkSync Era) network. -#[derive(Debug, Clone, Copy)] -pub struct L2(()); +#[derive(Debug, Clone, Copy, Default)] +pub struct L2(pub L2ChainId); -impl Network for L2 {} +impl Network for L2 { + fn metric_label(&self) -> String { + format!("l2_{}", self.0.as_u64()) + } +} -/// Associates a type with a particular network. +/// Associates a type with a particular RPC network, such as Ethereum or zkSync Era. RPC traits created using `jsonrpseee::rpc` +/// can use `ForNetwork` as a client boundary to restrict which implementations can call their methods. pub trait ForNetwork { /// Network that the type is associated with. type Net: Network; @@ -29,9 +46,12 @@ impl ForNetwork for Box { type Net = T::Net; } +/// Client that can be tagged with the component using it. pub trait TaggedClient: ForNetwork { - /// Sets the component operating this client. This is used in logging etc. + /// Tags this client as working for a specific component. The component name can be used in logging, + /// metrics etc. The component name should be copied to the clones of this client, but should not be passed upstream. fn for_component(self, component_name: &'static str) -> Self; + /// Returns the component tag. fn component(&self) -> &'static str; } diff --git a/core/lib/web3_decl/src/client/tests.rs b/core/lib/web3_decl/src/client/tests.rs index 6f6f0a56823..985ea0fe435 100644 --- a/core/lib/web3_decl/src/client/tests.rs +++ b/core/lib/web3_decl/src/client/tests.rs @@ -13,6 +13,7 @@ use jsonrpsee::{ }; use rand::{rngs::StdRng, Rng, SeedableRng}; use test_casing::test_casing; +use zksync_types::L2ChainId; use super::{ metrics::{HttpErrorLabels, RequestLabels, RpcErrorLabels}, @@ -215,10 +216,14 @@ async fn wrapping_mock_client() { }) }); - let mut client = ClientBuilder::::new(client, "http://localhost".parse().unwrap()) - .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) - .build() - .for_component("test"); + let mut client = ClientBuilder::new( + client, + L2(L2ChainId::default()), + "http://localhost".parse().unwrap(), + ) + .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) + .build() + .for_component("test"); let metrics = &*Box::leak(Box::default()); client.metrics = metrics; assert_eq!( @@ -242,11 +247,13 @@ async fn wrapping_mock_client() { // Check that the batch hit the rate limit. let labels = RequestLabels { + network: "l2_270".to_string(), component: "test", method: "ok".to_string(), }; assert!(metrics.rate_limit_latency.contains(&labels), "{metrics:?}"); let labels = RequestLabels { + network: "l2_270".to_string(), component: "test", method: "slow".to_string(), }; @@ -259,6 +266,7 @@ async fn wrapping_mock_client() { .unwrap_err(); assert_matches!(err, Error::Call(_)); let labels = RpcErrorLabels { + network: "l2_270".to_string(), component: "test", method: "unknown".to_string(), code: ErrorCode::MethodNotFound.code(), @@ -271,6 +279,7 @@ async fn wrapping_mock_client() { .unwrap_err(); assert_matches!(err, Error::Transport(_)); let labels = HttpErrorLabels { + network: "l2_270".to_string(), component: "test", method: "rate_limit".to_string(), status: Some(429), diff --git a/core/lib/zksync_core/src/api_server/web3/tests/mod.rs b/core/lib/zksync_core/src/api_server/web3/tests/mod.rs index e8c38053612..70fa8d5e531 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/mod.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/mod.rs @@ -362,9 +362,12 @@ async fn test_http_server(test: impl HttpTest) { .await; let local_addr = server_handles.wait_until_ready().await; - let client = Client::::http(format!("http://{local_addr}/").parse().unwrap()) - .unwrap() - .build(); + let client = Client::http( + L2::default(), + format!("http://{local_addr}/").parse().unwrap(), + ) + .unwrap() + .build(); test.test(&client, &pool).await.unwrap(); stop_sender.send_replace(true); diff --git a/core/lib/zksync_core/src/api_server/web3/tests/ws.rs b/core/lib/zksync_core/src/api_server/web3/tests/ws.rs index 97190a72e96..a3f2e6c3d75 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/ws.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/ws.rs @@ -185,7 +185,7 @@ async fn test_ws_server(test: impl WsTest) { .await; let local_addr = server_handles.wait_until_ready().await; - let client = Client::ws(format!("ws://{local_addr}").parse().unwrap()) + let client = Client::ws(L2::default(), format!("ws://{local_addr}").parse().unwrap()) .await .unwrap() .build(); diff --git a/core/lib/zksync_core/src/consensus/testonly.rs b/core/lib/zksync_core/src/consensus/testonly.rs index b500020b23e..4b277fa77c8 100644 --- a/core/lib/zksync_core/src/consensus/testonly.rs +++ b/core/lib/zksync_core/src/consensus/testonly.rs @@ -282,9 +282,12 @@ impl StateKeeper { let addr = sync::wait_for(ctx, &mut self.addr.clone(), Option::is_some) .await? .unwrap(); - let client = Client::::http(format!("http://{addr}/").parse().context("url")?) - .context("json_rpc()")? - .build(); + let client = Client::http( + L2::default(), + format!("http://{addr}/").parse().context("url")?, + ) + .context("json_rpc()")? + .build(); Ok(Box::new(client)) } diff --git a/core/lib/zksync_core/src/lib.rs b/core/lib/zksync_core/src/lib.rs index 37b0fdc2ec3..65b9ae5b7f1 100644 --- a/core/lib/zksync_core/src/lib.rs +++ b/core/lib/zksync_core/src/lib.rs @@ -300,7 +300,7 @@ pub async fn initialize_components( panic!("Circuit breaker triggered: {}", err); }); - let query_client = Client::::http(eth.web3_url.clone()) + let query_client = Client::http(L1(genesis_config.l1_chain_id), eth.web3_url.clone()) .context("Ethereum client")? .build(); let query_client = Box::new(query_client); @@ -313,6 +313,7 @@ pub async fn initialize_components( }; let mut gas_adjuster = GasAdjusterSingleton::new( + genesis_config.l1_chain_id, eth.web3_url.clone(), gas_adjuster_config, sender.pubdata_sending_mode, diff --git a/core/node/eth_watch/src/client.rs b/core/node/eth_watch/src/client.rs index 177a538194a..9e7eab9b95e 100644 --- a/core/node/eth_watch/src/client.rs +++ b/core/node/eth_watch/src/client.rs @@ -114,7 +114,7 @@ impl EthClient for EthHttpQueryClient { // This code is compatible with both Infura and Alchemy API providers. // Note: we don't handle rate-limits here - assumption is that we're never going to hit them. if let Err(EthClientError::EthereumGateway(err)) = &result { - tracing::warn!("Provider returned error message: {:?}", err); + tracing::warn!("Provider returned error message: {err}"); let err_message = err.as_ref().to_string(); let err_code = if let ClientError::Call(err) = err.as_ref() { Some(err.code()) diff --git a/core/node/fee_model/src/l1_gas_price/singleton.rs b/core/node/fee_model/src/l1_gas_price/singleton.rs index b9ac88858ba..5531b1bfe8c 100644 --- a/core/node/fee_model/src/l1_gas_price/singleton.rs +++ b/core/node/fee_model/src/l1_gas_price/singleton.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::Context as _; use tokio::{sync::watch, task::JoinHandle}; use zksync_config::{configs::eth_sender::PubdataSendingMode, GasAdjusterConfig}; -use zksync_types::url::SensitiveUrl; +use zksync_types::{url::SensitiveUrl, L1ChainId}; use zksync_web3_decl::client::{Client, L1}; use super::PubdataPricing; @@ -13,6 +13,7 @@ use crate::l1_gas_price::GasAdjuster; /// This is needed only for running the server. #[derive(Debug)] pub struct GasAdjusterSingleton { + chain_id: L1ChainId, web3_url: SensitiveUrl, gas_adjuster_config: GasAdjusterConfig, pubdata_sending_mode: PubdataSendingMode, @@ -22,12 +23,14 @@ pub struct GasAdjusterSingleton { impl GasAdjusterSingleton { pub fn new( + chain_id: L1ChainId, web3_url: SensitiveUrl, gas_adjuster_config: GasAdjusterConfig, pubdata_sending_mode: PubdataSendingMode, pubdata_pricing: Arc, ) -> Self { Self { + chain_id, web3_url, gas_adjuster_config, pubdata_sending_mode, @@ -40,7 +43,7 @@ impl GasAdjusterSingleton { if let Some(adjuster) = &self.singleton { Ok(adjuster.clone()) } else { - let query_client = Client::::http(self.web3_url.clone()) + let query_client = Client::http(L1(self.chain_id), self.web3_url.clone()) .context("QueryClient::new()")? .build(); let adjuster = GasAdjuster::new( diff --git a/core/node/node_framework/examples/main_node.rs b/core/node/node_framework/examples/main_node.rs index b524664c89a..520df2e28e7 100644 --- a/core/node/node_framework/examples/main_node.rs +++ b/core/node/node_framework/examples/main_node.rs @@ -103,8 +103,10 @@ impl MainNodeBuilder { } fn add_query_eth_client_layer(mut self) -> anyhow::Result { - let eth_client_config = EthConfig::from_env()?; - let query_eth_client_layer = QueryEthClientLayer::new(eth_client_config.web3_url); + let genesis = GenesisConfig::from_env()?; + let eth_config = EthConfig::from_env()?; + let query_eth_client_layer = + QueryEthClientLayer::new(genesis.l1_chain_id, eth_config.web3_url); self.node.add_layer(query_eth_client_layer); Ok(self) } diff --git a/core/node/node_framework/src/implementations/layers/query_eth_client.rs b/core/node/node_framework/src/implementations/layers/query_eth_client.rs index decee0140e3..e81abd15d22 100644 --- a/core/node/node_framework/src/implementations/layers/query_eth_client.rs +++ b/core/node/node_framework/src/implementations/layers/query_eth_client.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use zksync_types::url::SensitiveUrl; +use zksync_types::{url::SensitiveUrl, L1ChainId}; use zksync_web3_decl::client::{Client, L1}; use crate::{ @@ -10,12 +10,13 @@ use crate::{ #[derive(Debug)] pub struct QueryEthClientLayer { + chain_id: L1ChainId, web3_url: SensitiveUrl, } impl QueryEthClientLayer { - pub fn new(web3_url: SensitiveUrl) -> Self { - Self { web3_url } + pub fn new(chain_id: L1ChainId, web3_url: SensitiveUrl) -> Self { + Self { chain_id, web3_url } } } @@ -26,7 +27,7 @@ impl WiringLayer for QueryEthClientLayer { } async fn wire(self: Box, mut context: ServiceContext<'_>) -> Result<(), WiringError> { - let query_client = Client::::http(self.web3_url.clone()) + let query_client = Client::http(L1(self.chain_id), self.web3_url.clone()) .context("Client::new()")? .build(); context.insert_resource(EthInterfaceResource(Box::new(query_client)))?; diff --git a/core/tests/loadnext/src/account_pool.rs b/core/tests/loadnext/src/account_pool.rs index bb46699a31a..9d5c0f33094 100644 --- a/core/tests/loadnext/src/account_pool.rs +++ b/core/tests/loadnext/src/account_pool.rs @@ -94,7 +94,8 @@ impl AccountPool { let l2_chain_id = L2ChainId::try_from(config.l2_chain_id) .map_err(|err| anyhow::anyhow!("invalid L2 chain ID: {err}"))?; // Create a client for pinging the RPC. - let client = Client::::http( + let client = Client::http( + L2(l2_chain_id), config .l2_rpc_address .parse() diff --git a/core/tests/loadnext/src/sdk/ethereum/mod.rs b/core/tests/loadnext/src/sdk/ethereum/mod.rs index 0a81e79facf..70a3a48cb68 100644 --- a/core/tests/loadnext/src/sdk/ethereum/mod.rs +++ b/core/tests/loadnext/src/sdk/ethereum/mod.rs @@ -86,6 +86,7 @@ impl EthereumProvider { "Chain id overflow - Expected chain id to be in range 0..2^64".to_owned(), ) })?; + let l1_chain_id = L1ChainId(l1_chain_id); let contract_address = provider.get_main_contract().await?; let default_bridges = provider @@ -97,7 +98,7 @@ impl EthereumProvider { .as_ref() .parse::() .map_err(|err| ClientError::NetworkError(err.to_string()))?; - let query_client = Client::::http(eth_web3_url) + let query_client = Client::http(L1(l1_chain_id), eth_web3_url) .map_err(|err| ClientError::NetworkError(err.to_string()))? .build(); let eth_client = SigningClient::new( @@ -107,7 +108,7 @@ impl EthereumProvider { eth_signer, contract_address, DEFAULT_PRIORITY_FEE.into(), - L1ChainId(l1_chain_id), + l1_chain_id, ); let erc20_abi = ierc20_contract(); let l1_erc20_bridge_abi = l1_erc20_bridge_contract(); diff --git a/core/tests/loadnext/src/sdk/wallet.rs b/core/tests/loadnext/src/sdk/wallet.rs index 8a99a1166c5..ce23e4dd58d 100644 --- a/core/tests/loadnext/src/sdk/wallet.rs +++ b/core/tests/loadnext/src/sdk/wallet.rs @@ -42,7 +42,7 @@ where let rpc_address = rpc_address .parse() .map_err(|err| ClientError::NetworkError(format!("error parsing RPC url: {err}")))?; - let client = Client::http(rpc_address) + let client = Client::http(L2(signer.chain_id), rpc_address) .map_err(|err| ClientError::NetworkError(err.to_string()))? .build(); From 5d9bb59d251554f6257c558d8aa28a2dfd7cfb1c Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 8 May 2024 23:22:52 +0300 Subject: [PATCH 05/12] Rework network specifiers --- core/bin/block_reverter/src/main.rs | 7 ++-- core/bin/external_node/src/main.rs | 8 ++-- core/bin/zksync_server/src/main.rs | 5 ++- core/lib/web3_decl/src/client/mod.rs | 17 ++++++--- core/lib/web3_decl/src/client/network.rs | 37 +++++++++++++++---- core/lib/web3_decl/src/client/tests.rs | 14 +++---- .../src/api_server/web3/tests/mod.rs | 9 ++--- .../src/api_server/web3/tests/ws.rs | 2 +- .../lib/zksync_core/src/consensus/testonly.rs | 9 ++--- core/lib/zksync_core/src/lib.rs | 5 ++- .../fee_model/src/l1_gas_price/singleton.rs | 5 ++- .../layers/query_eth_client.rs | 5 ++- core/tests/loadnext/src/account_pool.rs | 2 +- core/tests/loadnext/src/sdk/ethereum/mod.rs | 5 ++- core/tests/loadnext/src/sdk/wallet.rs | 3 +- 15 files changed, 78 insertions(+), 55 deletions(-) diff --git a/core/bin/block_reverter/src/main.rs b/core/bin/block_reverter/src/main.rs index 29349f94804..ddd29c04839 100644 --- a/core/bin/block_reverter/src/main.rs +++ b/core/bin/block_reverter/src/main.rs @@ -5,7 +5,7 @@ use clap::{Parser, Subcommand}; use tokio::io::{self, AsyncReadExt}; use zksync_block_reverter::{ eth_client::{ - clients::{Client, PKSigningClient, L1}, + clients::{Client, PKSigningClient}, EthInterface, }, BlockReverter, BlockReverterEthConfig, NodeRole, @@ -129,8 +129,7 @@ async fn main() -> anyhow::Result<()> { json, operator_address, } => { - // FIXME: doesn't work - let eth_client = Client::http(L1(0.into()), eth_sender.web3_url.clone()) + let eth_client = Client::http(eth_sender.web3_url.clone()) .context("Ethereum client")? .build(); @@ -148,7 +147,7 @@ async fn main() -> anyhow::Result<()> { priority_fee_per_gas, nonce, } => { - let eth_client = Client::http(L1(0.into()), eth_sender.web3_url.clone()) + let eth_client = Client::http(eth_sender.web3_url.clone()) .context("Ethereum client")? .build(); #[allow(deprecated)] diff --git a/core/bin/external_node/src/main.rs b/core/bin/external_node/src/main.rs index b55032cfc86..7aad7b994d2 100644 --- a/core/bin/external_node/src/main.rs +++ b/core/bin/external_node/src/main.rs @@ -56,7 +56,7 @@ use zksync_storage::RocksDB; use zksync_types::L2ChainId; use zksync_utils::wait_for_tasks::ManagedTasks; use zksync_web3_decl::{ - client::{Client, DynClient, L1, L2}, + client::{Client, DynClient, L2}, jsonrpsee, namespaces::EnNamespaceClient, }; @@ -821,15 +821,17 @@ async fn main() -> anyhow::Result<()> { // Build L1 and L2 clients. let main_node_url = &required_config.main_node_url; tracing::info!("Main node URL is: {main_node_url:?}"); - let main_node_client = Client::http(L2(required_config.l2_chain_id), main_node_url.clone()) + let main_node_client = Client::http(main_node_url.clone()) .context("Failed creating JSON-RPC client for main node")? + .for_network(required_config.l2_chain_id.into()) .with_allowed_requests_per_second(optional_config.main_node_rate_limit_rps) .build(); let main_node_client = Box::new(main_node_client) as Box>; let eth_client_url = &required_config.eth_client_url; - let eth_client = Client::http(L1(required_config.l1_chain_id), eth_client_url.clone()) + let eth_client = Client::http(eth_client_url.clone()) .context("failed creating JSON-RPC client for Ethereum")? + .for_network(required_config.l1_chain_id.into()) .build(); let eth_client = Box::new(eth_client); diff --git a/core/bin/zksync_server/src/main.rs b/core/bin/zksync_server/src/main.rs index 606d0b419a0..02219bbed78 100644 --- a/core/bin/zksync_server/src/main.rs +++ b/core/bin/zksync_server/src/main.rs @@ -24,7 +24,7 @@ use zksync_core::{ Component, Components, }; use zksync_env_config::FromEnv; -use zksync_eth_client::clients::{Client, L1}; +use zksync_eth_client::clients::Client; use zksync_storage::RocksDB; use zksync_utils::wait_for_tasks::ManagedTasks; @@ -181,8 +181,9 @@ async fn main() -> anyhow::Result<()> { if let Some(ecosystem_contracts) = &contracts_config.ecosystem_contracts { let eth_config = configs.eth.as_ref().context("eth config")?; - let query_client = Client::http(L1(genesis.l1_chain_id), eth_config.web3_url.clone()) + let query_client = Client::http(eth_config.web3_url.clone()) .context("Ethereum client")? + .for_network(genesis.l1_chain_id.into()) .build(); zksync_node_genesis::save_set_chain_id_tx( &query_client, diff --git a/core/lib/web3_decl/src/client/mod.rs b/core/lib/web3_decl/src/client/mod.rs index b9368401ffd..b9e3a71d981 100644 --- a/core/lib/web3_decl/src/client/mod.rs +++ b/core/lib/web3_decl/src/client/mod.rs @@ -139,22 +139,21 @@ impl fmt::Debug for Client { impl Client { /// Creates an HTTP-backed client. - pub fn http(network: Net, url: SensitiveUrl) -> anyhow::Result> { + pub fn http(url: SensitiveUrl) -> anyhow::Result> { let client = HttpClientBuilder::default().build(url.expose_str())?; - Ok(ClientBuilder::new(client, network, url)) + Ok(ClientBuilder::new(client, url)) } } impl WsClient { /// Creates a WS-backed client. pub async fn ws( - network: Net, url: SensitiveUrl, ) -> anyhow::Result>> { let client = ws_client::WsClientBuilder::default() .build(url.expose_str()) .await?; - Ok(ClientBuilder::new(Shared::new(client), network, url)) + Ok(ClientBuilder::new(Shared::new(client), url)) } } @@ -328,15 +327,21 @@ impl fmt::Debug for ClientBuilder { impl ClientBuilder { /// Wraps the provided client. - fn new(client: C, network: Net, url: SensitiveUrl) -> Self { + fn new(client: C, url: SensitiveUrl) -> Self { Self { client, url, rate_limit: (1, Duration::ZERO), - network, + network: Net::default(), } } + /// Specifies the network to be used by the client. The network is logged and is used as a metrics label. + pub fn for_network(mut self, network: Net) -> Self { + self.network = network; + self + } + /// Sets the rate limit for the client. The rate limit is applied across all client instances, /// including cloned ones. pub fn with_allowed_requests_per_second(mut self, rps: NonZeroUsize) -> Self { diff --git a/core/lib/web3_decl/src/client/network.rs b/core/lib/web3_decl/src/client/network.rs index b4dcd847995..d8f1f7067a6 100644 --- a/core/lib/web3_decl/src/client/network.rs +++ b/core/lib/web3_decl/src/client/network.rs @@ -6,28 +6,49 @@ use zksync_types::{L1ChainId, L2ChainId}; /// Marker trait for networks. Two standard network kinds are [`L1`] and [`L2`]. /// -/// The `Display` implementation will be used in logging. -pub trait Network: 'static + Copy + Sync + Send + fmt::Debug { +/// The `Default` value should belong to a "generic" / "unknown" network, rather than to a specific network like the mainnet. +pub trait Network: 'static + Copy + Default + Sync + Send + fmt::Debug { + /// String representation of a network used as a metric label and in log messages. fn metric_label(&self) -> String; } /// L1 (i.e., Ethereum) network. -#[derive(Debug, Clone, Copy)] -pub struct L1(pub L1ChainId); +#[derive(Debug, Clone, Copy, Default)] +pub struct L1(Option); impl Network for L1 { fn metric_label(&self) -> String { - format!("ethereum_{}", self.0) + if let Some(chain_id) = self.0 { + format!("ethereum_{chain_id}") + } else { + "ethereum".to_owned() + } + } +} + +impl From for L1 { + fn from(chain_id: L1ChainId) -> Self { + Self(Some(chain_id)) } } -/// L2 (i.e., zkSync Era) network. +/// L2 network. #[derive(Debug, Clone, Copy, Default)] -pub struct L2(pub L2ChainId); +pub struct L2(Option); impl Network for L2 { fn metric_label(&self) -> String { - format!("l2_{}", self.0.as_u64()) + if let Some(chain_id) = self.0 { + format!("l2_{}", chain_id.as_u64()) + } else { + "l2".to_owned() + } + } +} + +impl From for L2 { + fn from(chain_id: L2ChainId) -> Self { + Self(Some(chain_id)) } } diff --git a/core/lib/web3_decl/src/client/tests.rs b/core/lib/web3_decl/src/client/tests.rs index 985ea0fe435..fc24ba4a865 100644 --- a/core/lib/web3_decl/src/client/tests.rs +++ b/core/lib/web3_decl/src/client/tests.rs @@ -13,7 +13,6 @@ use jsonrpsee::{ }; use rand::{rngs::StdRng, Rng, SeedableRng}; use test_casing::test_casing; -use zksync_types::L2ChainId; use super::{ metrics::{HttpErrorLabels, RequestLabels, RpcErrorLabels}, @@ -216,14 +215,11 @@ async fn wrapping_mock_client() { }) }); - let mut client = ClientBuilder::new( - client, - L2(L2ChainId::default()), - "http://localhost".parse().unwrap(), - ) - .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) - .build() - .for_component("test"); + let mut client = ClientBuilder::::new(client, "http://localhost".parse().unwrap()) + .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) + .build() + .for_component("test"); + let metrics = &*Box::leak(Box::default()); client.metrics = metrics; assert_eq!( diff --git a/core/lib/zksync_core/src/api_server/web3/tests/mod.rs b/core/lib/zksync_core/src/api_server/web3/tests/mod.rs index 70fa8d5e531..a0088c6cb3e 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/mod.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/mod.rs @@ -362,12 +362,9 @@ async fn test_http_server(test: impl HttpTest) { .await; let local_addr = server_handles.wait_until_ready().await; - let client = Client::http( - L2::default(), - format!("http://{local_addr}/").parse().unwrap(), - ) - .unwrap() - .build(); + let client = Client::http(format!("http://{local_addr}/").parse().unwrap()) + .unwrap() + .build(); test.test(&client, &pool).await.unwrap(); stop_sender.send_replace(true); diff --git a/core/lib/zksync_core/src/api_server/web3/tests/ws.rs b/core/lib/zksync_core/src/api_server/web3/tests/ws.rs index a3f2e6c3d75..97190a72e96 100644 --- a/core/lib/zksync_core/src/api_server/web3/tests/ws.rs +++ b/core/lib/zksync_core/src/api_server/web3/tests/ws.rs @@ -185,7 +185,7 @@ async fn test_ws_server(test: impl WsTest) { .await; let local_addr = server_handles.wait_until_ready().await; - let client = Client::ws(L2::default(), format!("ws://{local_addr}").parse().unwrap()) + let client = Client::ws(format!("ws://{local_addr}").parse().unwrap()) .await .unwrap() .build(); diff --git a/core/lib/zksync_core/src/consensus/testonly.rs b/core/lib/zksync_core/src/consensus/testonly.rs index 4b277fa77c8..e4831255052 100644 --- a/core/lib/zksync_core/src/consensus/testonly.rs +++ b/core/lib/zksync_core/src/consensus/testonly.rs @@ -282,12 +282,9 @@ impl StateKeeper { let addr = sync::wait_for(ctx, &mut self.addr.clone(), Option::is_some) .await? .unwrap(); - let client = Client::http( - L2::default(), - format!("http://{addr}/").parse().context("url")?, - ) - .context("json_rpc()")? - .build(); + let client = Client::http(format!("http://{addr}/").parse().context("url")?) + .context("json_rpc()")? + .build(); Ok(Box::new(client)) } diff --git a/core/lib/zksync_core/src/lib.rs b/core/lib/zksync_core/src/lib.rs index 161c4f50fac..de298eb1984 100644 --- a/core/lib/zksync_core/src/lib.rs +++ b/core/lib/zksync_core/src/lib.rs @@ -76,7 +76,7 @@ use zksync_object_store::{ObjectStore, ObjectStoreFactory}; use zksync_shared_metrics::{InitStage, APP_METRICS}; use zksync_state::{PostgresStorageCaches, RocksdbStorageOptions}; use zksync_types::{ethabi::Contract, fee_model::FeeModelConfig, Address, L2ChainId}; -use zksync_web3_decl::client::{Client, L1}; +use zksync_web3_decl::client::Client; use crate::{ api_server::{ @@ -291,8 +291,9 @@ pub async fn initialize_components( panic!("Circuit breaker triggered: {}", err); }); - let query_client = Client::http(L1(genesis_config.l1_chain_id), eth.web3_url.clone()) + let query_client = Client::http(eth.web3_url.clone()) .context("Ethereum client")? + .for_network(genesis_config.l1_chain_id.into()) .build(); let query_client = Box::new(query_client); let gas_adjuster_config = eth.gas_adjuster.context("gas_adjuster")?; diff --git a/core/node/fee_model/src/l1_gas_price/singleton.rs b/core/node/fee_model/src/l1_gas_price/singleton.rs index 5531b1bfe8c..fc148a67ced 100644 --- a/core/node/fee_model/src/l1_gas_price/singleton.rs +++ b/core/node/fee_model/src/l1_gas_price/singleton.rs @@ -4,7 +4,7 @@ use anyhow::Context as _; use tokio::{sync::watch, task::JoinHandle}; use zksync_config::{configs::eth_sender::PubdataSendingMode, GasAdjusterConfig}; use zksync_types::{url::SensitiveUrl, L1ChainId}; -use zksync_web3_decl::client::{Client, L1}; +use zksync_web3_decl::client::Client; use super::PubdataPricing; use crate::l1_gas_price::GasAdjuster; @@ -43,8 +43,9 @@ impl GasAdjusterSingleton { if let Some(adjuster) = &self.singleton { Ok(adjuster.clone()) } else { - let query_client = Client::http(L1(self.chain_id), self.web3_url.clone()) + let query_client = Client::http(self.web3_url.clone()) .context("QueryClient::new()")? + .for_network(self.chain_id.into()) .build(); let adjuster = GasAdjuster::new( Box::new(query_client), diff --git a/core/node/node_framework/src/implementations/layers/query_eth_client.rs b/core/node/node_framework/src/implementations/layers/query_eth_client.rs index e81abd15d22..0e4be369db4 100644 --- a/core/node/node_framework/src/implementations/layers/query_eth_client.rs +++ b/core/node/node_framework/src/implementations/layers/query_eth_client.rs @@ -1,6 +1,6 @@ use anyhow::Context; use zksync_types::{url::SensitiveUrl, L1ChainId}; -use zksync_web3_decl::client::{Client, L1}; +use zksync_web3_decl::client::Client; use crate::{ implementations::resources::eth_interface::EthInterfaceResource, @@ -27,8 +27,9 @@ impl WiringLayer for QueryEthClientLayer { } async fn wire(self: Box, mut context: ServiceContext<'_>) -> Result<(), WiringError> { - let query_client = Client::http(L1(self.chain_id), self.web3_url.clone()) + let query_client = Client::http(self.web3_url.clone()) .context("Client::new()")? + .for_network(self.chain_id.into()) .build(); context.insert_resource(EthInterfaceResource(Box::new(query_client)))?; Ok(()) diff --git a/core/tests/loadnext/src/account_pool.rs b/core/tests/loadnext/src/account_pool.rs index 9d5c0f33094..1ea6f61b9df 100644 --- a/core/tests/loadnext/src/account_pool.rs +++ b/core/tests/loadnext/src/account_pool.rs @@ -95,12 +95,12 @@ impl AccountPool { .map_err(|err| anyhow::anyhow!("invalid L2 chain ID: {err}"))?; // Create a client for pinging the RPC. let client = Client::http( - L2(l2_chain_id), config .l2_rpc_address .parse() .context("invalid L2 RPC URL")?, )? + .for_network(l2_chain_id.into()) .build(); // Perform a health check: check whether zkSync server is alive. let mut server_alive = false; diff --git a/core/tests/loadnext/src/sdk/ethereum/mod.rs b/core/tests/loadnext/src/sdk/ethereum/mod.rs index 70a3a48cb68..e031ed102e8 100644 --- a/core/tests/loadnext/src/sdk/ethereum/mod.rs +++ b/core/tests/loadnext/src/sdk/ethereum/mod.rs @@ -17,7 +17,7 @@ use zksync_types::{ Address, L1ChainId, L1TxCommonData, H160, H256, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, }; use zksync_web3_decl::{ - client::{Client, L1}, + client::Client, namespaces::{EthNamespaceClient, ZksNamespaceClient}, }; @@ -98,8 +98,9 @@ impl EthereumProvider { .as_ref() .parse::() .map_err(|err| ClientError::NetworkError(err.to_string()))?; - let query_client = Client::http(L1(l1_chain_id), eth_web3_url) + let query_client = Client::http(eth_web3_url) .map_err(|err| ClientError::NetworkError(err.to_string()))? + .for_network(l1_chain_id.into()) .build(); let eth_client = SigningClient::new( Box::new(query_client).for_component("provider"), diff --git a/core/tests/loadnext/src/sdk/wallet.rs b/core/tests/loadnext/src/sdk/wallet.rs index ce23e4dd58d..3c58b63dac7 100644 --- a/core/tests/loadnext/src/sdk/wallet.rs +++ b/core/tests/loadnext/src/sdk/wallet.rs @@ -42,8 +42,9 @@ where let rpc_address = rpc_address .parse() .map_err(|err| ClientError::NetworkError(format!("error parsing RPC url: {err}")))?; - let client = Client::http(L2(signer.chain_id), rpc_address) + let client = Client::http(rpc_address) .map_err(|err| ClientError::NetworkError(err.to_string()))? + .for_network(signer.chain_id.into()) .build(); Ok(Wallet { From 344ddca6588051d8a81f3976678822fbf86245c6 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 9 May 2024 16:21:07 +0300 Subject: [PATCH 06/12] Fix L1 client use in prover workspace --- prover/prover_cli/src/commands/status/l1.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/prover/prover_cli/src/commands/status/l1.rs b/prover/prover_cli/src/commands/status/l1.rs index 19e5c3c4fa1..adc2cb64a0d 100644 --- a/prover/prover_cli/src/commands/status/l1.rs +++ b/prover/prover_cli/src/commands/status/l1.rs @@ -7,14 +7,17 @@ use zksync_basic_types::{ use zksync_config::{ContractsConfig, EthConfig, PostgresConfig}; use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_env_config::FromEnv; -use zksync_eth_client::{clients::QueryClient, CallFunctionArgs}; +use zksync_eth_client::{ + clients::{Client, L1}, + CallFunctionArgs, +}; pub(crate) async fn run() -> anyhow::Result<()> { println!(" ====== L1 Status ====== "); let postgres_config = PostgresConfig::from_env().context("PostgresConfig::from_env")?; let contracts_config = ContractsConfig::from_env().context("ContractsConfig::from_env()")?; let eth_config = EthConfig::from_env().context("EthConfig::from_env")?; - let query_client = QueryClient::new(eth_config.web3_url)?; + let query_client = Client::::http(eth_config.web3_url)?.build(); let total_batches_committed: U256 = CallFunctionArgs::new("getTotalBatchesCommitted", ()) .for_contract( From b2cf4ec32d7b1d8f50fe3510a14a628dfa7f18cf Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 9 May 2024 16:22:35 +0300 Subject: [PATCH 07/12] Disable features for `web_decl` crate --- core/bin/external_node/Cargo.toml | 2 +- core/lib/eth_client/Cargo.toml | 2 +- core/lib/snapshots_applier/Cargo.toml | 2 +- core/lib/web3_decl/Cargo.toml | 2 +- core/node/fee_model/Cargo.toml | 4 +-- core/node/node_framework/Cargo.toml | 2 +- core/tests/loadnext/Cargo.toml | 2 +- prover/Cargo.lock | 35 --------------------------- 8 files changed, 8 insertions(+), 43 deletions(-) diff --git a/core/bin/external_node/Cargo.toml b/core/bin/external_node/Cargo.toml index b496dfd72c7..6c1927a9db3 100644 --- a/core/bin/external_node/Cargo.toml +++ b/core/bin/external_node/Cargo.toml @@ -28,7 +28,7 @@ zksync_snapshots_applier.workspace = true zksync_object_store.workspace = true prometheus_exporter.workspace = true zksync_health_check.workspace = true -zksync_web3_decl.workspace = true +zksync_web3_decl = { workspace = true, features = ["client"] } zksync_types.workspace = true zksync_block_reverter.workspace = true zksync_shared_metrics.workspace = true diff --git a/core/lib/eth_client/Cargo.toml b/core/lib/eth_client/Cargo.toml index 783501d171a..4e407a4b0dc 100644 --- a/core/lib/eth_client/Cargo.toml +++ b/core/lib/eth_client/Cargo.toml @@ -15,7 +15,7 @@ zksync_types.workspace = true zksync_eth_signer.workspace = true zksync_config.workspace = true zksync_contracts.workspace = true -zksync_web3_decl.workspace = true +zksync_web3_decl = { workspace = true, features = ["client"] } thiserror.workspace = true async-trait.workspace = true diff --git a/core/lib/snapshots_applier/Cargo.toml b/core/lib/snapshots_applier/Cargo.toml index a293b7714b9..7062f65699f 100644 --- a/core/lib/snapshots_applier/Cargo.toml +++ b/core/lib/snapshots_applier/Cargo.toml @@ -15,7 +15,7 @@ zksync_dal.workspace = true zksync_health_check.workspace = true zksync_types.workspace = true zksync_object_store.workspace = true -zksync_web3_decl.workspace = true +zksync_web3_decl = { workspace = true, features = ["client"] } zksync_utils.workspace = true vise.workspace = true diff --git a/core/lib/web3_decl/Cargo.toml b/core/lib/web3_decl/Cargo.toml index fec212c500a..359d09f6fc3 100644 --- a/core/lib/web3_decl/Cargo.toml +++ b/core/lib/web3_decl/Cargo.toml @@ -34,6 +34,6 @@ test-casing.workspace = true tokio = { workspace = true, features = ["rt", "test-util"] } [features] -default = ["server", "client"] +default = [] server = ["jsonrpsee/server"] client = ["jsonrpsee/client"] diff --git a/core/node/fee_model/Cargo.toml b/core/node/fee_model/Cargo.toml index 55453c7fae2..06b19a4f6a2 100644 --- a/core/node/fee_model/Cargo.toml +++ b/core/node/fee_model/Cargo.toml @@ -16,7 +16,7 @@ zksync_dal.workspace = true zksync_config.workspace = true zksync_eth_client.workspace = true zksync_utils.workspace = true -zksync_web3_decl.workspace = true +zksync_web3_decl = { workspace = true, features = ["client"] } tokio = { workspace = true, features = ["time"] } anyhow.workspace = true @@ -25,4 +25,4 @@ tracing.workspace = true [dev-dependencies] test-casing.workspace = true -zksync_node_test_utils.workspace = true \ No newline at end of file +zksync_node_test_utils.workspace = true diff --git a/core/node/node_framework/Cargo.toml b/core/node/node_framework/Cargo.toml index e02266c1654..1bbb89ce9f7 100644 --- a/core/node/node_framework/Cargo.toml +++ b/core/node/node_framework/Cargo.toml @@ -24,7 +24,7 @@ zksync_core.workspace = true zksync_storage.workspace = true zksync_eth_client.workspace = true zksync_contracts.workspace = true -zksync_web3_decl.workspace = true +zksync_web3_decl = { workspace = true, features = ["client"] } zksync_utils.workspace = true zksync_circuit_breaker.workspace = true zksync_concurrency.workspace = true diff --git a/core/tests/loadnext/Cargo.toml b/core/tests/loadnext/Cargo.toml index 3fdf92d3b52..804a4b9e1cd 100644 --- a/core/tests/loadnext/Cargo.toml +++ b/core/tests/loadnext/Cargo.toml @@ -14,7 +14,7 @@ publish = false zksync_types.workspace = true zksync_utils.workspace = true zksync_eth_signer.workspace = true -zksync_web3_decl.workspace = true +zksync_web3_decl = { workspace = true, features = ["client"] } zksync_eth_client.workspace = true zksync_config.workspace = true zksync_contracts.workspace = true diff --git a/prover/Cargo.lock b/prover/Cargo.lock index a994f45f468..b9b31a72af8 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -2905,11 +2905,9 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", "jsonrpsee-proc-macros", - "jsonrpsee-server", "jsonrpsee-types", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", - "tokio", "tracing", ] @@ -2951,9 +2949,7 @@ dependencies = [ "futures-util", "hyper", "jsonrpsee-types", - "parking_lot", "pin-project", - "rand 0.8.5", "rustc-hash", "serde", "serde_json", @@ -2997,30 +2993,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "jsonrpsee-server" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc7c6d1a2c58f6135810284a390d9f823d0f508db74cd914d8237802de80f98" -dependencies = [ - "futures-util", - "http", - "hyper", - "jsonrpsee-core", - "jsonrpsee-types", - "pin-project", - "route-recognizer", - "serde", - "serde_json", - "soketto", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tower", - "tracing", -] - [[package]] name = "jsonrpsee-types" version = "0.21.0" @@ -5027,12 +4999,6 @@ dependencies = [ "librocksdb-sys", ] -[[package]] -name = "route-recognizer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" - [[package]] name = "rsa" version = "0.9.6" @@ -5796,7 +5762,6 @@ dependencies = [ "base64 0.13.1", "bytes", "futures 0.3.30", - "http", "httparse", "log", "rand 0.8.5", From 2a192eea297f258dbfa49682fa830a366ac84c5b Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 9 May 2024 18:07:14 +0300 Subject: [PATCH 08/12] Fix and test client cloning --- core/lib/eth_client/src/clients/http/query.rs | 16 +++++++++++ core/lib/web3_decl/src/client/boxed.rs | 28 +++++++++++++++++-- core/lib/web3_decl/src/client/tests.rs | 2 ++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/core/lib/eth_client/src/clients/http/query.rs b/core/lib/eth_client/src/clients/http/query.rs index e4f81a8e8d9..0d7b44ba391 100644 --- a/core/lib/eth_client/src/clients/http/query.rs +++ b/core/lib/eth_client/src/clients/http/query.rs @@ -327,3 +327,19 @@ where Ok(block) } } + +#[cfg(test)] +mod tests { + use zksync_web3_decl::client::Client; + + use super::*; + + #[test] + fn client_can_be_cloned() { + let client = Client::::http("http://localhost".parse().unwrap()) + .unwrap() + .build(); + let client: Box = Box::new(client); + let _ = client.clone(); + } +} diff --git a/core/lib/web3_decl/src/client/boxed.rs b/core/lib/web3_decl/src/client/boxed.rs index 8691d60275c..57485b1bc9e 100644 --- a/core/lib/web3_decl/src/client/boxed.rs +++ b/core/lib/web3_decl/src/client/boxed.rs @@ -30,6 +30,8 @@ impl ToRpcParams for RawParams { /// /// The implementation is fairly straightforward: [`RawParams`] is used as a catch-all params type, /// and `serde_json::Value` is used as a catch-all response type. +#[doc(hidden)] +// ^ The internals of this trait are considered implementation details; it's only exposed via `DynClient` type alias #[async_trait] pub trait ObjectSafeClient: 'static + Send + Sync + fmt::Debug + ForNetwork { fn clone_boxed(&self) -> Box>; @@ -57,7 +59,7 @@ where C: 'static + Send + Sync + Clone + fmt::Debug + ClientT + TaggedClient, { fn clone_boxed(&self) -> Box::Net>> { - Box::new(self.clone()) + Box::new(::clone(self)) } fn for_component( @@ -87,11 +89,12 @@ where } } +/// Dynamically typed RPC client for a certain [`Network`]. pub type DynClient = dyn ObjectSafeClient; -impl Clone for Box> { +impl Clone for Box> { fn clone(&self) -> Self { - self.clone_boxed() + self.as_ref().clone_boxed() } } @@ -205,4 +208,23 @@ mod tests { let block_number = client.as_ref().get_block_number().await.unwrap(); assert_eq!(block_number, 0x42.into()); } + + #[tokio::test] + async fn client_can_be_cloned() { + let client = MockClient::new(|method, params| { + assert_eq!(method, "eth_blockNumber"); + assert_eq!(params, serde_json::Value::Null); + Ok(serde_json::json!("0x42")) + }); + let client = Box::new(client) as Box>; + + let cloned_client = client.clone(); + let block_number = cloned_client.get_block_number().await.unwrap(); + assert_eq!(block_number, 0x42.into()); + + let client_with_label = client.for_component("test"); + assert_eq!(TaggedClient::component(&client_with_label), "test"); + let block_number = client_with_label.get_block_number().await.unwrap(); + assert_eq!(block_number, 0x42.into()); + } } diff --git a/core/lib/web3_decl/src/client/tests.rs b/core/lib/web3_decl/src/client/tests.rs index fc24ba4a865..2907c5a0088 100644 --- a/core/lib/web3_decl/src/client/tests.rs +++ b/core/lib/web3_decl/src/client/tests.rs @@ -13,6 +13,7 @@ use jsonrpsee::{ }; use rand::{rngs::StdRng, Rng, SeedableRng}; use test_casing::test_casing; +use zksync_types::L2ChainId; use super::{ metrics::{HttpErrorLabels, RequestLabels, RpcErrorLabels}, @@ -216,6 +217,7 @@ async fn wrapping_mock_client() { }); let mut client = ClientBuilder::::new(client, "http://localhost".parse().unwrap()) + .for_network(L2ChainId::default().into()) .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) .build() .for_component("test"); From 9b0abfc236bd0a12a4fa7bd15a38653b2e0da26d Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 9 May 2024 18:18:52 +0300 Subject: [PATCH 09/12] Fix `web3_decl` features --- core/lib/web3_decl/Cargo.toml | 1 + core/lib/web3_decl/src/namespaces/debug.rs | 15 ++++--- core/lib/web3_decl/src/namespaces/en.rs | 5 ++- core/lib/web3_decl/src/namespaces/eth.rs | 41 ++++++++++++------- core/lib/web3_decl/src/namespaces/mod.rs | 4 +- core/lib/web3_decl/src/namespaces/net.rs | 5 ++- .../lib/web3_decl/src/namespaces/snapshots.rs | 5 ++- core/lib/web3_decl/src/namespaces/web3.rs | 5 ++- core/lib/web3_decl/src/namespaces/zks.rs | 11 ++--- prover/prover_cli/src/commands/status/l1.rs | 2 +- 10 files changed, 62 insertions(+), 32 deletions(-) diff --git a/core/lib/web3_decl/Cargo.toml b/core/lib/web3_decl/Cargo.toml index 359d09f6fc3..baf8b2a9aaa 100644 --- a/core/lib/web3_decl/Cargo.toml +++ b/core/lib/web3_decl/Cargo.toml @@ -16,6 +16,7 @@ rlp.workspace = true thiserror.workspace = true jsonrpsee = { workspace = true, features = [ "macros", + "client-core", ] } pin-project-lite.workspace = true zksync_types.workspace = true diff --git a/core/lib/web3_decl/src/namespaces/debug.rs b/core/lib/web3_decl/src/namespaces/debug.rs index b5ee49b5eba..ed8131012b8 100644 --- a/core/lib/web3_decl/src/namespaces/debug.rs +++ b/core/lib/web3_decl/src/namespaces/debug.rs @@ -1,14 +1,15 @@ -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; use zksync_types::{ api::{BlockId, BlockNumber, DebugCall, ResultDebugCall, TracerConfig}, debug_flat_call::DebugCallFlat, transaction_request::CallRequest, }; -use crate::{ - client::{ForNetwork, L2}, - types::H256, -}; +#[cfg(feature = "client")] +use crate::client::{ForNetwork, L2}; +use crate::types::H256; #[cfg_attr( all(feature = "client", feature = "server"), @@ -29,18 +30,21 @@ pub trait DebugNamespace { block: BlockNumber, options: Option, ) -> RpcResult>; + #[method(name = "traceBlockByNumber.callFlatTracer")] async fn trace_block_by_number_flat( &self, block: BlockNumber, options: Option, ) -> RpcResult>; + #[method(name = "traceBlockByHash")] async fn trace_block_by_hash( &self, hash: H256, options: Option, ) -> RpcResult>; + #[method(name = "traceCall")] async fn trace_call( &self, @@ -48,6 +52,7 @@ pub trait DebugNamespace { block: Option, options: Option, ) -> RpcResult; + #[method(name = "traceTransaction")] async fn trace_transaction( &self, diff --git a/core/lib/web3_decl/src/namespaces/en.rs b/core/lib/web3_decl/src/namespaces/en.rs index 65d31b6dcd9..3c2f9b7e80e 100644 --- a/core/lib/web3_decl/src/namespaces/en.rs +++ b/core/lib/web3_decl/src/namespaces/en.rs @@ -1,7 +1,10 @@ -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; use zksync_config::{configs::EcosystemContracts, GenesisConfig}; use zksync_types::{api::en, tokens::TokenInfo, Address, L2BlockNumber}; +#[cfg(feature = "client")] use crate::client::{ForNetwork, L2}; #[cfg_attr( diff --git a/core/lib/web3_decl/src/namespaces/eth.rs b/core/lib/web3_decl/src/namespaces/eth.rs index a7701c3d872..0c0307a0195 100644 --- a/core/lib/web3_decl/src/namespaces/eth.rs +++ b/core/lib/web3_decl/src/namespaces/eth.rs @@ -1,19 +1,17 @@ -use jsonrpsee::{ - core::{RpcResult, SubscriptionResult}, - proc_macros::rpc, -}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; use zksync_types::{ api::{BlockId, BlockIdVariant, BlockNumber, Transaction, TransactionVariant}, transaction_request::CallRequest, Address, H256, }; -use crate::{ - client::{ForNetwork, L2}, - types::{ - Block, Bytes, FeeHistory, Filter, FilterChanges, Index, Log, PubSubFilter, SyncState, - TransactionReceipt, U256, U64, - }, +#[cfg(feature = "client")] +use crate::client::{ForNetwork, L2}; +use crate::types::{ + Block, Bytes, FeeHistory, Filter, FilterChanges, Index, Log, SyncState, TransactionReceipt, + U256, U64, }; #[cfg_attr( @@ -179,9 +177,22 @@ pub trait EthNamespace { ) -> RpcResult; } -#[rpc(server, namespace = "eth")] -pub trait EthPubSub { - #[subscription(name = "subscribe" => "subscription", unsubscribe = "unsubscribe", item = PubSubResult)] - async fn subscribe(&self, sub_type: String, filter: Option) - -> SubscriptionResult; +#[cfg(feature = "server")] +mod pub_sub { + use jsonrpsee::{core::SubscriptionResult, proc_macros::rpc}; + + use crate::types::PubSubFilter; + + #[rpc(server, namespace = "eth")] + pub trait EthPubSub { + #[subscription(name = "subscribe" => "subscription", unsubscribe = "unsubscribe", item = PubSubResult)] + async fn subscribe( + &self, + sub_type: String, + filter: Option, + ) -> SubscriptionResult; + } } + +#[cfg(feature = "server")] +pub use self::pub_sub::EthPubSubServer; diff --git a/core/lib/web3_decl/src/namespaces/mod.rs b/core/lib/web3_decl/src/namespaces/mod.rs index bca5d29ccfe..9515745e79e 100644 --- a/core/lib/web3_decl/src/namespaces/mod.rs +++ b/core/lib/web3_decl/src/namespaces/mod.rs @@ -1,13 +1,13 @@ #[cfg(feature = "client")] pub use self::{ debug::DebugNamespaceClient, en::EnNamespaceClient, eth::EthNamespaceClient, - net::NetNamespaceClient, snapshots::SnapshotsNamespaceServer, web3::Web3NamespaceClient, + net::NetNamespaceClient, snapshots::SnapshotsNamespaceClient, web3::Web3NamespaceClient, zks::ZksNamespaceClient, }; #[cfg(feature = "server")] pub use self::{ debug::DebugNamespaceServer, en::EnNamespaceServer, eth::EthNamespaceServer, - eth::EthPubSubServer, net::NetNamespaceServer, snapshots::SnapshotsNamespaceClient, + eth::EthPubSubServer, net::NetNamespaceServer, snapshots::SnapshotsNamespaceServer, web3::Web3NamespaceServer, zks::ZksNamespaceServer, }; diff --git a/core/lib/web3_decl/src/namespaces/net.rs b/core/lib/web3_decl/src/namespaces/net.rs index 3a3880f8de9..2cd27bcad66 100644 --- a/core/lib/web3_decl/src/namespaces/net.rs +++ b/core/lib/web3_decl/src/namespaces/net.rs @@ -1,6 +1,9 @@ -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; use zksync_types::U256; +#[cfg(feature = "client")] use crate::client::{ForNetwork, L2}; #[cfg_attr( diff --git a/core/lib/web3_decl/src/namespaces/snapshots.rs b/core/lib/web3_decl/src/namespaces/snapshots.rs index de9b89580d6..2fbed2dc7e7 100644 --- a/core/lib/web3_decl/src/namespaces/snapshots.rs +++ b/core/lib/web3_decl/src/namespaces/snapshots.rs @@ -1,9 +1,12 @@ -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; use zksync_types::{ snapshots::{AllSnapshots, SnapshotHeader}, L1BatchNumber, }; +#[cfg(feature = "client")] use crate::client::{ForNetwork, L2}; #[cfg_attr( diff --git a/core/lib/web3_decl/src/namespaces/web3.rs b/core/lib/web3_decl/src/namespaces/web3.rs index c8d9d44ff68..700443b0c7c 100644 --- a/core/lib/web3_decl/src/namespaces/web3.rs +++ b/core/lib/web3_decl/src/namespaces/web3.rs @@ -1,5 +1,8 @@ -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; +#[cfg(feature = "client")] use crate::client::{ForNetwork, L2}; #[cfg_attr( diff --git a/core/lib/web3_decl/src/namespaces/zks.rs b/core/lib/web3_decl/src/namespaces/zks.rs index 0ae98a6130c..79f91eafb8b 100644 --- a/core/lib/web3_decl/src/namespaces/zks.rs +++ b/core/lib/web3_decl/src/namespaces/zks.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +#[cfg_attr(not(feature = "server"), allow(unused_imports))] +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; use zksync_types::{ api::{ BlockDetails, BridgeAddresses, L1BatchDetails, L2ToL1LogProof, Proof, ProtocolVersion, @@ -12,10 +14,9 @@ use zksync_types::{ Address, L1BatchNumber, L2BlockNumber, H256, U256, U64, }; -use crate::{ - client::{ForNetwork, L2}, - types::{Bytes, Token}, -}; +#[cfg(feature = "client")] +use crate::client::{ForNetwork, L2}; +use crate::types::{Bytes, Token}; #[cfg_attr( all(feature = "client", feature = "server"), diff --git a/prover/prover_cli/src/commands/status/l1.rs b/prover/prover_cli/src/commands/status/l1.rs index adc2cb64a0d..5299c1bc0a2 100644 --- a/prover/prover_cli/src/commands/status/l1.rs +++ b/prover/prover_cli/src/commands/status/l1.rs @@ -17,7 +17,7 @@ pub(crate) async fn run() -> anyhow::Result<()> { let postgres_config = PostgresConfig::from_env().context("PostgresConfig::from_env")?; let contracts_config = ContractsConfig::from_env().context("ContractsConfig::from_env()")?; let eth_config = EthConfig::from_env().context("EthConfig::from_env")?; - let query_client = Client::::http(eth_config.web3_url)?.build(); + let query_client = Client::::http(eth_config.web3_url)?.build(); let total_batches_committed: U256 = CallFunctionArgs::new("getTotalBatchesCommitted", ()) .for_contract( From 73f0432fefa05d2235debbe8fcd3f700902d5d8b Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 10 May 2024 10:43:17 +0300 Subject: [PATCH 10/12] Brush up client traits --- core/lib/eth_client/src/clients/http/query.rs | 5 +- core/lib/web3_decl/src/client/boxed.rs | 83 ++++++++++--------- core/lib/web3_decl/src/client/mock.rs | 13 +-- core/lib/web3_decl/src/client/mod.rs | 15 ++-- core/lib/web3_decl/src/client/network.rs | 21 +++-- core/lib/web3_decl/src/client/tests.rs | 4 +- 6 files changed, 80 insertions(+), 61 deletions(-) diff --git a/core/lib/eth_client/src/clients/http/query.rs b/core/lib/eth_client/src/clients/http/query.rs index 0d7b44ba391..d8f8b4e19ee 100644 --- a/core/lib/eth_client/src/clients/http/query.rs +++ b/core/lib/eth_client/src/clients/http/query.rs @@ -23,8 +23,9 @@ where Box::new(self.clone()) } - fn for_component(self: Box, component_name: &'static str) -> Box { - Box::new(TaggedClient::for_component(*self, component_name)) + fn for_component(mut self: Box, component_name: &'static str) -> Box { + self.set_component(component_name); + self } async fn fetch_chain_id(&self) -> Result { diff --git a/core/lib/web3_decl/src/client/boxed.rs b/core/lib/web3_decl/src/client/boxed.rs index 57485b1bc9e..c977b5a6534 100644 --- a/core/lib/web3_decl/src/client/boxed.rs +++ b/core/lib/web3_decl/src/client/boxed.rs @@ -26,62 +26,72 @@ impl ToRpcParams for RawParams { } } -/// Object-safe version of [`ClientT`] + [`Clone`]. -/// -/// The implementation is fairly straightforward: [`RawParams`] is used as a catch-all params type, -/// and `serde_json::Value` is used as a catch-all response type. -#[doc(hidden)] -// ^ The internals of this trait are considered implementation details; it's only exposed via `DynClient` type alias +/// Object-safe version of [`ClientT`] + [`Clone`]. Should generally be used via [`DynClient`] type alias. +// The implementation is fairly straightforward: [`RawParams`] is used as a catch-all params type, +// and `serde_json::Value` is used as a catch-all response type. #[async_trait] pub trait ObjectSafeClient: 'static + Send + Sync + fmt::Debug + ForNetwork { - fn clone_boxed(&self) -> Box>; - - fn for_component( - self: Box, - component_name: &'static str, - ) -> Box>; + /// Tags this client as working for a specific component. The component name can be used in logging, + /// metrics etc. + fn for_component(self: Box, component_name: &'static str) -> Box>; + /// Returns the component tag previously set with [`Self::for_component()`]. fn component(&self) -> &'static str; - async fn notification(&self, method: &str, params: RawParams) -> Result<(), Error>; + #[doc(hidden)] // implementation detail + fn clone_boxed(&self) -> Box>; + + #[doc(hidden)] // implementation detail + async fn generic_notification(&self, method: &str, params: RawParams) -> Result<(), Error>; - async fn request(&self, method: &str, params: RawParams) -> Result; + #[doc(hidden)] // implementation detail + async fn generic_request( + &self, + method: &str, + params: RawParams, + ) -> Result; - async fn batch_request<'a>( + #[doc(hidden)] // implementation detail + async fn generic_batch_request<'a>( &self, batch: BatchRequestBuilder<'a>, ) -> Result, Error>; } +/// Dynamically typed RPC client for a certain [`Network`]. +pub type DynClient = dyn ObjectSafeClient; + #[async_trait] impl ObjectSafeClient for C where C: 'static + Send + Sync + Clone + fmt::Debug + ClientT + TaggedClient, { - fn clone_boxed(&self) -> Box::Net>> { + fn clone_boxed(&self) -> Box> { Box::new(::clone(self)) } - fn for_component( - self: Box, - component_name: &'static str, - ) -> Box::Net>> { - Box::new(TaggedClient::for_component(*self, component_name)) + fn for_component(mut self: Box, component_name: &'static str) -> Box> { + self.set_component(component_name); + self } fn component(&self) -> &'static str { TaggedClient::component(self) } - async fn notification(&self, method: &str, params: RawParams) -> Result<(), Error> { + async fn generic_notification(&self, method: &str, params: RawParams) -> Result<(), Error> { ::notification(self, method, params).await } - async fn request(&self, method: &str, params: RawParams) -> Result { + async fn generic_request( + &self, + method: &str, + params: RawParams, + ) -> Result { ::request(self, method, params).await } - async fn batch_request<'a>( + async fn generic_batch_request<'a>( &self, batch: BatchRequestBuilder<'a>, ) -> Result, Error> { @@ -89,9 +99,6 @@ where } } -/// Dynamically typed RPC client for a certain [`Network`]. -pub type DynClient = dyn ObjectSafeClient; - impl Clone for Box> { fn clone(&self) -> Self { self.as_ref().clone_boxed() @@ -104,7 +111,9 @@ impl ClientT for &DynClient { where Params: ToRpcParams + Send, { - (**self).notification(method, RawParams::new(params)?).await + (**self) + .generic_notification(method, RawParams::new(params)?) + .await } async fn request(&self, method: &str, params: Params) -> Result @@ -112,7 +121,9 @@ impl ClientT for &DynClient { R: DeserializeOwned, Params: ToRpcParams + Send, { - let raw_response = (**self).request(method, RawParams::new(params)?).await?; + let raw_response = (**self) + .generic_request(method, RawParams::new(params)?) + .await?; serde_json::from_value(raw_response).map_err(Error::ParseError) } @@ -123,7 +134,7 @@ impl ClientT for &DynClient { where R: DeserializeOwned + fmt::Debug + 'a, { - let raw_responses = (**self).batch_request(batch).await?; + let raw_responses = (**self).generic_batch_request(batch).await?; let mut successful_calls = 0; let mut failed_calls = 0; let mut responses = Vec::with_capacity(raw_responses.len()); @@ -176,16 +187,6 @@ impl ClientT for Box> { } } -impl TaggedClient for Box> { - fn for_component(self, component_name: &'static str) -> Self { - ObjectSafeClient::for_component(self, component_name) - } - - fn component(&self) -> &'static str { - (**self).component() - } -} - #[cfg(test)] mod tests { use super::*; @@ -223,7 +224,7 @@ mod tests { assert_eq!(block_number, 0x42.into()); let client_with_label = client.for_component("test"); - assert_eq!(TaggedClient::component(&client_with_label), "test"); + assert_eq!(client_with_label.component(), "test"); let block_number = client_with_label.get_block_number().await.unwrap(); assert_eq!(block_number, 0x42.into()); } diff --git a/core/lib/web3_decl/src/client/mock.rs b/core/lib/web3_decl/src/client/mock.rs index 74837c0f0fa..2a0f54ecc1a 100644 --- a/core/lib/web3_decl/src/client/mock.rs +++ b/core/lib/web3_decl/src/client/mock.rs @@ -64,17 +64,20 @@ impl MockClient { impl ForNetwork for MockClient { type Net = Net; -} -impl TaggedClient for MockClient { - fn for_component(mut self, component_name: &'static str) -> Self { - self.component_name = component_name; - self + fn network(&self) -> Self::Net { + Net::default() } +} +impl TaggedClient for MockClient { fn component(&self) -> &'static str { self.component_name } + + fn set_component(&mut self, component_name: &'static str) { + self.component_name = component_name; + } } #[async_trait] diff --git a/core/lib/web3_decl/src/client/mod.rs b/core/lib/web3_decl/src/client/mod.rs index b9e3a71d981..090d766f8f5 100644 --- a/core/lib/web3_decl/src/client/mod.rs +++ b/core/lib/web3_decl/src/client/mod.rs @@ -36,7 +36,7 @@ use zksync_types::url::SensitiveUrl; use self::metrics::{L2ClientMetrics, METRICS}; pub use self::{ - boxed::DynClient, + boxed::{DynClient, ObjectSafeClient}, mock::MockClient, network::{ForNetwork, Network, TaggedClient, L1, L2}, shared::Shared, @@ -224,17 +224,20 @@ impl Client { impl ForNetwork for Client { type Net = Net; -} -impl TaggedClient for Client { - fn for_component(mut self, component_name: &'static str) -> Self { - self.component_name = component_name; - self + fn network(&self) -> Self::Net { + self.network } +} +impl TaggedClient for Client { fn component(&self) -> &'static str { self.component_name } + + fn set_component(&mut self, component_name: &'static str) { + self.component_name = component_name; + } } #[async_trait] diff --git a/core/lib/web3_decl/src/client/network.rs b/core/lib/web3_decl/src/client/network.rs index d8f1f7067a6..bc95a40e67a 100644 --- a/core/lib/web3_decl/src/client/network.rs +++ b/core/lib/web3_decl/src/client/network.rs @@ -52,27 +52,38 @@ impl From for L2 { } } -/// Associates a type with a particular RPC network, such as Ethereum or zkSync Era. RPC traits created using `jsonrpseee::rpc` +/// Associates a type with a particular type of RPC networks, such as Ethereum or zkSync Era. RPC traits created using `jsonrpsee::rpc` /// can use `ForNetwork` as a client boundary to restrict which implementations can call their methods. pub trait ForNetwork { /// Network that the type is associated with. type Net: Network; + + /// Returns a network for this type instance. + fn network(&self) -> Self::Net; } impl ForNetwork for &T { type Net = T::Net; + + fn network(&self) -> Self::Net { + (**self).network() + } } impl ForNetwork for Box { type Net = T::Net; + + fn network(&self) -> Self::Net { + self.as_ref().network() + } } /// Client that can be tagged with the component using it. pub trait TaggedClient: ForNetwork { - /// Tags this client as working for a specific component. The component name can be used in logging, - /// metrics etc. The component name should be copied to the clones of this client, but should not be passed upstream. - fn for_component(self, component_name: &'static str) -> Self; - /// Returns the component tag. fn component(&self) -> &'static str; + + /// Tags this client as working for a specific component. The component name can be used in logging, + /// metrics etc. The component name should be copied to the clones of this client, but should not be passed upstream. + fn set_component(&mut self, component_name: &'static str); } diff --git a/core/lib/web3_decl/src/client/tests.rs b/core/lib/web3_decl/src/client/tests.rs index 2907c5a0088..4e709590afb 100644 --- a/core/lib/web3_decl/src/client/tests.rs +++ b/core/lib/web3_decl/src/client/tests.rs @@ -219,8 +219,8 @@ async fn wrapping_mock_client() { let mut client = ClientBuilder::::new(client, "http://localhost".parse().unwrap()) .for_network(L2ChainId::default().into()) .with_allowed_requests_per_second(NonZeroUsize::new(100).unwrap()) - .build() - .for_component("test"); + .build(); + client.set_component("test"); let metrics = &*Box::leak(Box::default()); client.metrics = metrics; From b75eba430581f49f30faa2f7ce33780545160bde Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 10 May 2024 15:34:09 +0300 Subject: [PATCH 11/12] Brush up `MockClient` --- core/bin/external_node/src/helpers.rs | 27 ++-- core/bin/external_node/src/tests.rs | 60 ++++---- core/lib/web3_decl/src/client/boxed.rs | 20 ++- core/lib/web3_decl/src/client/mock.rs | 198 ++++++++++++++++++++----- core/lib/web3_decl/src/client/tests.rs | 48 +++--- 5 files changed, 226 insertions(+), 127 deletions(-) diff --git a/core/bin/external_node/src/helpers.rs b/core/bin/external_node/src/helpers.rs index f4cff1541e4..dede538cebe 100644 --- a/core/bin/external_node/src/helpers.rs +++ b/core/bin/external_node/src/helpers.rs @@ -226,11 +226,10 @@ mod tests { #[tokio::test] async fn validating_chain_ids_errors() { - let main_node_client = MockClient::::new(|method, _| match method { - "eth_chainId" => Ok(serde_json::json!(U64::from(270))), - "zks_L1ChainId" => Ok(serde_json::json!(U64::from(3))), - _ => panic!("unexpected L2 call: {method}"), - }); + let main_node_client = MockClient::builder(L2::default()) + .method("eth_chainId", || Ok(U64::from(270))) + .method("zks_L1ChainId", || Ok(U64::from(3))) + .build(); let validation_task = ValidateChainIdsTask::new( L1ChainId(3), // << mismatch with the Ethereum client @@ -265,11 +264,10 @@ mod tests { "{err}" ); - let main_node_client = MockClient::::new(|method, _| match method { - "eth_chainId" => Ok(serde_json::json!(U64::from(270))), - "zks_L1ChainId" => Ok(serde_json::json!(U64::from(9))), - _ => panic!("unexpected L2 call: {method}"), - }); + let main_node_client = MockClient::builder(L2::default()) + .method("eth_chainId", || Ok(U64::from(270))) + .method("zks_L1ChainId", || Ok(U64::from(9))) + .build(); let validation_task = ValidateChainIdsTask::new( L1ChainId(9), @@ -290,11 +288,10 @@ mod tests { #[tokio::test] async fn validating_chain_ids_success() { - let main_node_client = MockClient::::new(|method, _| match method { - "eth_chainId" => Ok(serde_json::json!(U64::from(270))), - "zks_L1ChainId" => Ok(serde_json::json!(U64::from(9))), - _ => panic!("unexpected L2 call: {method}"), - }); + let main_node_client = MockClient::builder(L2::default()) + .method("eth_chainId", || Ok(U64::from(270))) + .method("zks_L1ChainId", || Ok(U64::from(9))) + .build(); let validation_task = ValidateChainIdsTask::new( L1ChainId(9), diff --git a/core/bin/external_node/src/tests.rs b/core/bin/external_node/src/tests.rs index e4650e48593..b29e5ab6457 100644 --- a/core/bin/external_node/src/tests.rs +++ b/core/bin/external_node/src/tests.rs @@ -5,7 +5,9 @@ use test_casing::test_casing; use zksync_basic_types::protocol_version::ProtocolVersionId; use zksync_eth_client::clients::MockEthereum; use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; -use zksync_types::{api, ethabi, fee_model::FeeParams, L1BatchNumber, L2BlockNumber, H256, U64}; +use zksync_types::{ + api, ethabi, fee_model::FeeParams, Address, L1BatchNumber, L2BlockNumber, H256, U64, +}; use zksync_web3_decl::client::MockClient; use super::*; @@ -131,39 +133,31 @@ async fn external_node_basics(components_str: &'static str) { let diamond_proxy_addr = config.remote.diamond_proxy_addr; - let l2_client = MockClient::::new(move |method, params| { - tracing::info!("Called L2 client: {method}({params:?})"); - match method { - "eth_chainId" => Ok(serde_json::json!(U64::from(270))), - "zks_L1ChainId" => Ok(serde_json::json!(U64::from(9))), - - "zks_L1BatchNumber" => Ok(serde_json::json!("0x0")), - "zks_getL1BatchDetails" => { - let (number,): (L1BatchNumber,) = serde_json::from_value(params)?; - assert_eq!(number, L1BatchNumber(0)); - Ok(serde_json::to_value(api::L1BatchDetails { - number: L1BatchNumber(0), - base: block_details_base(genesis_params.root_hash), - })?) - } - "eth_blockNumber" => Ok(serde_json::json!("0x0")), - "eth_getBlockByNumber" => { - let (number, _): (api::BlockNumber, bool) = serde_json::from_value(params)?; + let l2_client = MockClient::builder(L2::default()) + .method("eth_chainId", || Ok(U64::from(270))) + .method("zks_L1ChainId", || Ok(U64::from(9))) + .method("zks_L1BatchNumber", || Ok(U64::from(0))) + .method("zks_getL1BatchDetails", move |number: L1BatchNumber| { + assert_eq!(number, L1BatchNumber(0)); + Ok(api::L1BatchDetails { + number: L1BatchNumber(0), + base: block_details_base(genesis_params.root_hash), + }) + }) + .method("eth_blockNumber", || Ok(U64::from(0))) + .method( + "eth_getBlockByNumber", + move |number: api::BlockNumber, _with_txs: bool| { assert_eq!(number, api::BlockNumber::Number(0.into())); - Ok(serde_json::to_value( - api::Block:: { - hash: genesis_l2_block.hash, - ..api::Block::default() - }, - )?) - } - - "zks_getFeeParams" => Ok(serde_json::to_value(FeeParams::sensible_v1_default())?), - "en_whitelistedTokensForAA" => Ok(serde_json::json!([])), - - _ => panic!("Unexpected call: {method}({params:?})"), - } - }); + Ok(api::Block:: { + hash: genesis_l2_block.hash, + ..api::Block::default() + }) + }, + ) + .method("zks_getFeeParams", || Ok(FeeParams::sensible_v1_default())) + .method("en_whitelistedTokensForAA", || Ok([] as [Address; 0])) + .build(); let l2_client = Box::new(l2_client); let eth_client = MockEthereum::default().with_call_handler(move |call, _| { diff --git a/core/lib/web3_decl/src/client/boxed.rs b/core/lib/web3_decl/src/client/boxed.rs index c977b5a6534..e1ad712da1f 100644 --- a/core/lib/web3_decl/src/client/boxed.rs +++ b/core/lib/web3_decl/src/client/boxed.rs @@ -12,7 +12,7 @@ use serde::de::DeserializeOwned; use super::{ForNetwork, Network, TaggedClient}; #[derive(Debug)] -pub struct RawParams(Option>); +pub struct RawParams(pub(super) Option>); impl RawParams { fn new(params: impl ToRpcParams) -> Result { @@ -189,6 +189,8 @@ impl ClientT for Box> { #[cfg(test)] mod tests { + use zksync_types::U64; + use super::*; use crate::{ client::{MockClient, L2}, @@ -197,11 +199,9 @@ mod tests { #[tokio::test] async fn boxing_mock_client() { - let client = MockClient::new(|method, params| { - assert_eq!(method, "eth_blockNumber"); - assert_eq!(params, serde_json::Value::Null); - Ok(serde_json::json!("0x42")) - }); + let client = MockClient::builder(L2::default()) + .method("eth_blockNumber", || Ok(U64::from(0x42))) + .build(); let client = Box::new(client) as Box>; let block_number = client.get_block_number().await.unwrap(); @@ -212,11 +212,9 @@ mod tests { #[tokio::test] async fn client_can_be_cloned() { - let client = MockClient::new(|method, params| { - assert_eq!(method, "eth_blockNumber"); - assert_eq!(params, serde_json::Value::Null); - Ok(serde_json::json!("0x42")) - }); + let client = MockClient::builder(L2::default()) + .method("eth_blockNumber", || Ok(U64::from(0x42))) + .build(); let client = Box::new(client) as Box>; let cloned_client = client.clone(); diff --git a/core/lib/web3_decl/src/client/mock.rs b/core/lib/web3_decl/src/client/mock.rs index 2a0f54ecc1a..b1815afcc96 100644 --- a/core/lib/web3_decl/src/client/mock.rs +++ b/core/lib/web3_decl/src/client/mock.rs @@ -1,29 +1,163 @@ //! Mock L2 client implementation. -use std::{fmt, future::Future, marker::PhantomData, pin::Pin, sync::Arc}; +use std::{any, collections::HashMap, fmt, future::Future, marker::PhantomData, sync::Arc}; use async_trait::async_trait; use futures::future; -use jsonrpsee::core::{ - client::{BatchResponse, ClientT, Error}, - params::BatchRequestBuilder, - traits::ToRpcParams, +use jsonrpsee::{ + core::{ + client::{BatchResponse, ClientT, Error}, + params::BatchRequestBuilder, + traits::ToRpcParams, + }, + types::{error::ErrorCode, ErrorObject}, }; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; -use super::{ForNetwork, Network, TaggedClient}; +use super::{boxed::RawParams, ForNetwork, Network, TaggedClient}; -type MockHandleResult<'a> = - Pin> + Send + 'a>>; -type RequestHandler = dyn Fn(&str, serde_json::Value) -> MockHandleResult<'_> + Send + Sync; +/// Object-safe counterpart to [`Handler`]. We need it because async closures aren't available on stable Rust. +#[async_trait] +trait HandleGenericRequest: Send + Sync + fmt::Debug { + async fn handle_generic_request( + &self, + req: serde_json::Value, + ) -> Result; +} + +/// The only implementation of [`HandleGenericRequest`]. +struct RequestHandler { + inner: H, + _fn: PhantomData, +} + +impl fmt::Debug for RequestHandler +where + Req: 'static, + H: Handler, + H::Resp: 'static, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("RequestHandler") + .field("async", &ASYNC) + .field("req", &any::type_name::()) + .field("resp", &any::type_name::()) + .finish() + } +} + +#[async_trait] +impl HandleGenericRequest for RequestHandler +where + Req: DeserializeOwned + Send + 'static, + H: Handler, + H::Resp: Serialize + 'static, +{ + async fn handle_generic_request( + &self, + req: serde_json::Value, + ) -> Result { + let req: Req = serde_json::from_value(req)?; + let resp = self.inner.call(req).await?; + Ok(serde_json::to_value(resp)?) + } +} + +/// Request handler for [`MockClient`]. Implemented automatically for sync and async closures +/// taking zero to 3 deserializable args (you might want to specify their types explicitly) +/// and returning `Result`, where `Resp` is serializable. +pub trait Handler: Send + Sync + 'static { + /// Successful response. + type Resp: Serialize + 'static; + /// Handler future. + type Fut: Future> + Send + 'static; + /// Calls the handler. + fn call(&self, req: Req) -> Self::Fut; +} + +macro_rules! impl_handler_for_tuple { + ($($field:tt : $typ:ident),*) => { + impl Handler<($($typ,)*), true> for F + where + F: Fn($($typ,)*) -> Result + Send + Sync + 'static, + $($typ: DeserializeOwned + 'static,)* + Resp: Serialize + Send + 'static, + { + type Resp = Resp; + type Fut = future::Ready>; + + #[allow(unused_variables)] // for `()` + fn call(&self, req: ($($typ,)*)) -> Self::Fut { + future::ready(self($(req.$field,)*)) + } + } + + impl Handler<($($typ,)*), false> for F + where + F: Fn($($typ,)*) -> Fut + Send + Sync + 'static, + Fut: Future> + Send + 'static, + $($typ: DeserializeOwned + 'static,)* + Resp: Serialize + Send + 'static, + { + type Resp = Resp; + type Fut = Fut; + + #[allow(unused_variables)] // for `()` + fn call(&self, req: ($($typ,)*)) -> Self::Fut { + self($(req.$field,)*) + } + } + }; +} + +impl_handler_for_tuple!(); +impl_handler_for_tuple!(0: A); +impl_handler_for_tuple!(0: A, 1: B); +impl_handler_for_tuple!(0: A, 1: B, 2: C); + +/// Builder for [`MockClient`]. +#[derive(Debug)] +pub struct MockClientBuilder { + request_handlers: HashMap<&'static str, Box>, + network: Net, +} + +impl MockClientBuilder { + /// Adds a mock method handler to this builder. + #[must_use] + pub fn method( + mut self, + method: &'static str, + request_handler: impl Handler, + ) -> Self + where + Req: DeserializeOwned + Send + 'static, + { + let handler = RequestHandler { + inner: request_handler, + _fn: PhantomData, + }; + self.request_handlers.insert(method, Box::new(handler)); + self + } + + pub fn build(self) -> MockClient { + MockClient { + request_handlers: Arc::new(self.request_handlers), + component_name: "", + network: self.network, + } + } +} /// Mock L2 client implementation. For now, it only mocks requests and batch requests; all other /// interactions with the client will panic. #[derive(Clone)] pub struct MockClient { - request_handler: Arc, + request_handlers: Arc>>, component_name: &'static str, - _network: PhantomData, + network: Net, } impl fmt::Debug for MockClient { @@ -35,29 +169,10 @@ impl fmt::Debug for MockClient { } impl MockClient { - /// Creates an L2 client based on the provided request handler. - pub fn new(request_handler: F) -> Self - where - F: Fn(&str, serde_json::Value) -> Result + Send + Sync + 'static, - { - Self { - request_handler: Arc::new(move |method, params| { - Box::pin(future::ready(request_handler(method, params))) - }), - component_name: "", - _network: PhantomData, - } - } - - /// Creates an L2 client based on the provided async request handler. - pub fn new_async(request_handler: F) -> Self - where - F: Fn(&str, serde_json::Value) -> MockHandleResult<'_> + Send + Sync + 'static, - { - Self { - request_handler: Arc::new(request_handler), - component_name: "", - _network: PhantomData, + pub fn builder(network: Net) -> MockClientBuilder { + MockClientBuilder { + request_handlers: HashMap::new(), + network, } } } @@ -66,7 +181,7 @@ impl ForNetwork for MockClient { type Net = Net; fn network(&self) -> Self::Net { - Net::default() + self.network } } @@ -100,7 +215,14 @@ impl ClientT for MockClient { } else { serde_json::Value::Null }; - let raw_response = (self.request_handler)(method, params).await?; + let handler = self.request_handlers.get(method).ok_or_else(|| { + Error::Call(ErrorObject::owned( + ErrorCode::MethodNotFound.code(), + ErrorCode::MethodNotFound.message(), + None::<()>, + )) + })?; + let raw_response = handler.handle_generic_request(params).await?; Ok(serde_json::from_value(raw_response)?) } @@ -113,7 +235,7 @@ impl ClientT for MockClient { { let request_handlers = batch .into_iter() - .map(|(method, _)| self.request::(method, [()])); + .map(|(method, value)| self.request(method, RawParams(value))); let response_results = future::join_all(request_handlers).await; let mut responses = vec![]; let mut successful_calls = 0; diff --git a/core/lib/web3_decl/src/client/tests.rs b/core/lib/web3_decl/src/client/tests.rs index 4e709590afb..2cb677514c7 100644 --- a/core/lib/web3_decl/src/client/tests.rs +++ b/core/lib/web3_decl/src/client/tests.rs @@ -7,13 +7,10 @@ use std::{ use assert_matches::assert_matches; use futures::future; -use jsonrpsee::{ - http_client::transport, - types::{error::ErrorCode, ErrorObject}, -}; +use jsonrpsee::{http_client::transport, rpc_params, types::error::ErrorCode}; use rand::{rngs::StdRng, Rng, SeedableRng}; use test_casing::test_casing; -use zksync_types::L2ChainId; +use zksync_types::{L2ChainId, U64}; use super::{ metrics::{HttpErrorLabels, RequestLabels, RpcErrorLabels}, @@ -194,27 +191,18 @@ async fn rate_limiting_with_rng_and_threads(rate_limit: usize) { async fn wrapping_mock_client() { tokio::time::pause(); - let client = MockClient::::new_async(|method, _| { - Box::pin(async move { - match method { - "ok" => Ok(serde_json::from_value(serde_json::json!("ok"))?), - "slow" => { - tokio::time::sleep(Duration::from_millis(10)).await; - Ok(serde_json::from_value(serde_json::json!("slow"))?) - } - "rate_limit" => { - let http_err = transport::Error::RequestFailure { status_code: 429 }; - Err(Error::Transport(http_err.into())) - } - "eth_getBlockNumber" => Ok(serde_json::from_value(serde_json::json!("0x1"))?), - _ => { - let unknown_method_err = - ErrorObject::borrowed(ErrorCode::MethodNotFound.code(), "oops", None); - Err(Error::Call(unknown_method_err)) - } - } + let client = MockClient::builder(L2::default()) + .method("ok", || Ok("ok")) + .method("slow", || async { + tokio::time::sleep(Duration::from_millis(10)).await; + Ok("slow") }) - }); + .method("rate_limit", || { + let http_err = transport::Error::RequestFailure { status_code: 429 }; + Err::<(), _>(Error::Transport(http_err.into())) + }) + .method("eth_getBlockNumber", || Ok(U64::from(1))) + .build(); let mut client = ClientBuilder::::new(client, "http://localhost".parse().unwrap()) .for_network(L2ChainId::default().into()) @@ -232,15 +220,15 @@ async fn wrapping_mock_client() { // Check that expected results are passed from the wrapped client. for _ in 0..10 { - let output: String = client.request("ok", [()]).await.unwrap(); + let output: String = client.request("ok", rpc_params![]).await.unwrap(); assert_eq!(output, "ok"); } let mut batch_request = BatchRequestBuilder::new(); for _ in 0..5 { - batch_request.insert("ok", [()]).unwrap(); + batch_request.insert("ok", rpc_params![]).unwrap(); } - batch_request.insert("slow", [()]).unwrap(); + batch_request.insert("slow", rpc_params![]).unwrap(); client.batch_request::(batch_request).await.unwrap(); // Check that the batch hit the rate limit. @@ -259,7 +247,7 @@ async fn wrapping_mock_client() { // Check error reporting. let err = client - .request::("unknown", [()]) + .request::("unknown", rpc_params![]) .await .unwrap_err(); assert_matches!(err, Error::Call(_)); @@ -272,7 +260,7 @@ async fn wrapping_mock_client() { assert!(metrics.rpc_errors.contains(&labels), "{metrics:?}"); let err = client - .request::("rate_limit", [()]) + .request::("rate_limit", rpc_params![]) .await .unwrap_err(); assert_matches!(err, Error::Transport(_)); From 1f2fd621b889f54257f553f1e10e2c332d6d4961 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 10 May 2024 16:42:30 +0300 Subject: [PATCH 12/12] Fix spelling --- core/lib/web3_decl/src/client/mock.rs | 2 +- prover/prover_cli/src/commands/status/utils.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lib/web3_decl/src/client/mock.rs b/core/lib/web3_decl/src/client/mock.rs index b1815afcc96..885962a9f5e 100644 --- a/core/lib/web3_decl/src/client/mock.rs +++ b/core/lib/web3_decl/src/client/mock.rs @@ -65,7 +65,7 @@ where } /// Request handler for [`MockClient`]. Implemented automatically for sync and async closures -/// taking zero to 3 deserializable args (you might want to specify their types explicitly) +/// taking zero to 3 de-serializable args (you might want to specify their types explicitly) /// and returning `Result`, where `Resp` is serializable. pub trait Handler: Send + Sync + 'static { /// Successful response. diff --git a/prover/prover_cli/src/commands/status/utils.rs b/prover/prover_cli/src/commands/status/utils.rs index 3312149359b..178fdc3761e 100644 --- a/prover/prover_cli/src/commands/status/utils.rs +++ b/prover/prover_cli/src/commands/status/utils.rs @@ -114,7 +114,7 @@ pub enum TaskStatus { JobsNotFound, } -// This implementation will change to From> for `AggregationRoundInfo` +// This implementation will change to `From>` for `AggregationRoundInfo` // once the --verbose flag is implemented. impl From> for TaskStatus { fn from(jobs_vector: Vec) -> Self {