diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index a76d569d..8ca208b0 100644 --- a/SUPPORTED_APIS.md +++ b/SUPPORTED_APIS.md @@ -14,6 +14,7 @@ The `status` options are: | Namespace | API |
Status
| Description | | --- | --- | --- | --- | +| `ANVIL` | `anvil_setLoggingEnabled` | `SUPPORTED` | Enables or disables logging | | `ANVIL` | `anvil_snapshot` | `SUPPORTED` | Snapshot the state of the blockchain at the current block | | `ANVIL` | `anvil_revert` | `SUPPORTED` | Revert the state of the blockchain to a previous snapshot | | `ANVIL` | `anvil_setTime` | `SUPPORTED` | Sets the internal clock time to the given timestamp | diff --git a/e2e-tests/test/anvil-apis.test.ts b/e2e-tests/test/anvil-apis.test.ts index 648ff80b..1f36470d 100644 --- a/e2e-tests/test/anvil-apis.test.ts +++ b/e2e-tests/test/anvil-apis.test.ts @@ -7,9 +7,34 @@ import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; import * as hre from "hardhat"; import { keccak256 } from "ethers/lib/utils"; import { BigNumber } from "ethers"; +import * as fs from "node:fs"; const provider = getTestProvider(); +describe("anvil_setLoggingEnabled", function () { + it("Should disable and enable logging", async function () { + // Arrange + const wallet = new Wallet(RichAccounts[0].PrivateKey, provider); + const userWallet = Wallet.createRandom().connect(provider); + + // Act + await provider.send("anvil_setLoggingEnabled", [false]); + const logSizeBefore = fs.statSync("../era_test_node.log").size; + + await wallet.sendTransaction({ + to: userWallet.address, + value: ethers.utils.parseEther("0.1"), + }); + const logSizeAfter = fs.statSync("../era_test_node.log").size; + + // Reset + await provider.send("anvil_setLoggingEnabled", [true]); + + // Assert + expect(logSizeBefore).to.equal(logSizeAfter); + }); +}); + describe("anvil_snapshot", function () { it("Should return incrementing snapshot ids", async function () { const wallet = new Wallet(RichAccounts[6].PrivateKey); diff --git a/src/namespaces/anvil.rs b/src/namespaces/anvil.rs index 39c802a0..29e4db88 100644 --- a/src/namespaces/anvil.rs +++ b/src/namespaces/anvil.rs @@ -6,6 +6,14 @@ use crate::utils::Numeric; #[rpc] pub trait AnvilNamespaceT { + /// Enable or disable logging. + /// + /// # Arguments + /// + /// * `enable` - if `true` logging will be enabled, disabled otherwise + #[rpc(name = "anvil_setLoggingEnabled")] + fn set_logging_enabled(&self, enable: bool) -> RpcResult<()>; + /// Snapshot the state of the blockchain at the current block. Takes no parameters. Returns the id of the snapshot /// that was created. A snapshot can only be reverted once. After a successful `anvil_revert`, the same snapshot id cannot /// be used again. Consider creating a new snapshot after each `anvil_revert` if you need to revert to the same diff --git a/src/node/anvil.rs b/src/node/anvil.rs index a3251f5a..b0d57003 100644 --- a/src/node/anvil.rs +++ b/src/node/anvil.rs @@ -12,6 +12,15 @@ use crate::{ impl AnvilNamespaceT for InMemoryNode { + fn set_logging_enabled(&self, enable: bool) -> RpcResult<()> { + self.set_logging_enabled(enable) + .map_err(|err| { + tracing::error!("failed creating snapshot: {:?}", err); + into_jsrpc_error(Web3Error::InternalError(err)) + }) + .into_boxed_future() + } + fn snapshot(&self) -> RpcResult { self.snapshot() .map_err(|err| { diff --git a/src/node/config_api.rs b/src/node/config_api.rs index 37bfb1ee..cbc03ccc 100644 --- a/src/node/config_api.rs +++ b/src/node/config_api.rs @@ -184,48 +184,46 @@ impl Configurat } fn config_set_log_level(&self, level: LogLevel) -> Result { - if let Some(observability) = &self - .get_inner() - .read() - .map_err(|err| { - tracing::error!("failed acquiring lock: {:?}", err); - into_jsrpc_error(Web3Error::InternalError(anyhow::Error::msg( - "Failed to acquire read lock for inner node state.", - ))) - })? - .observability - { - match observability.set_log_level(level) { - Ok(_) => tracing::info!("set log level to '{}'", level), - Err(err) => { - tracing::error!("failed setting log level {:?}", err); - return Ok(false); - } + let Some(observability) = &self.observability else { + return Err(into_jsrpc_error(Web3Error::InternalError( + anyhow::Error::msg("Node's logging is not set up."), + ))); + }; + match observability.set_log_level(level) { + Ok(_) => { + tracing::info!("set log level to '{}'", level); + self.get_inner() + .write() + .map_err(|err| { + tracing::error!("failed acquiring lock: {:?}", err); + into_jsrpc_error(Web3Error::InternalError(anyhow::Error::msg( + "Failed to acquire write lock for inner node state.", + ))) + })? + .config + .log_level = level; + Ok(true) + } + Err(err) => { + tracing::error!("failed setting log level {:?}", err); + Ok(false) } } - Ok(true) } fn config_set_logging(&self, directive: String) -> Result { - if let Some(observability) = &self - .get_inner() - .read() - .map_err(|err| { - tracing::error!("failed acquiring lock: {:?}", err); - into_jsrpc_error(Web3Error::InternalError(anyhow::Error::msg( - "Failed to acquire read lock for inner node state.", - ))) - })? - .observability - { - match observability.set_logging(&directive) { - Ok(_) => tracing::info!("set logging to '{}'", directive), - Err(err) => { - tracing::error!("failed setting logging to '{}': {:?}", directive, err); - return Ok(false); - } + let Some(observability) = &self.observability else { + return Err(into_jsrpc_error(Web3Error::InternalError( + anyhow::Error::msg("Node's logging is not set up."), + ))); + }; + match observability.set_logging(directive.clone()) { + Ok(_) => tracing::info!("set logging to '{}'", directive), + Err(err) => { + tracing::error!("failed setting logging to '{}': {:?}", directive, err); + return Ok(false); } - } + }; Ok(true) } } diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs index 80ce0896..9fef3219 100644 --- a/src/node/in_memory.rs +++ b/src/node/in_memory.rs @@ -225,8 +225,6 @@ pub struct InMemoryNodeInner { pub rich_accounts: HashSet, /// Keeps track of historical states indexed via block hash. Limited to [MAX_PREVIOUS_STATES]. pub previous_states: IndexMap>, - /// An optional handle to the observability stack - pub observability: Option, } #[derive(Debug)] @@ -238,11 +236,7 @@ pub struct TxExecutionOutput { impl InMemoryNodeInner { /// Create the state to be used implementing [InMemoryNode]. - pub fn new( - fork: Option, - observability: Option, - config: &TestNodeConfig, - ) -> Self { + pub fn new(fork: Option, config: &TestNodeConfig) -> Self { let updated_config = config.clone(); if let Some(f) = &fork { @@ -294,7 +288,6 @@ impl InMemoryNodeInner { impersonation: impersonation_manager, rich_accounts: HashSet::new(), previous_states: Default::default(), - observability, } } else { let mut block_hashes = HashMap::::new(); @@ -335,7 +328,6 @@ impl InMemoryNodeInner { impersonation: impersonation_manager, rich_accounts: HashSet::new(), previous_states: Default::default(), - observability, } } } @@ -891,6 +883,8 @@ pub struct InMemoryNode { pub(crate) system_contracts_options: system_contracts::Options, pub(crate) time: TimestampManager, pub(crate) impersonation: ImpersonationManager, + /// An optional handle to the observability stack + pub(crate) observability: Option, } fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs) -> Option { @@ -915,7 +909,7 @@ impl InMemoryNode { config: &TestNodeConfig, ) -> Self { let system_contracts_options = config.system_contracts_options; - let inner = InMemoryNodeInner::new(fork, observability, config); + let inner = InMemoryNodeInner::new(fork, config); let time = inner.time.clone(); let impersonation = inner.impersonation.clone(); InMemoryNode { @@ -924,6 +918,7 @@ impl InMemoryNode { system_contracts_options, time, impersonation, + observability, } } @@ -957,15 +952,8 @@ impl InMemoryNode { } pub fn reset(&self, fork: Option) -> Result<(), String> { - let observability = self - .inner - .read() - .map_err(|e| format!("Failed to acquire read lock: {}", e))? - .observability - .clone(); - let config = self.get_config()?; - let inner = InMemoryNodeInner::new(fork, observability, &config); + let inner = InMemoryNodeInner::new(fork, &config); let mut writer = self .snapshots diff --git a/src/node/in_memory_ext.rs b/src/node/in_memory_ext.rs index 6cc1c236..3d331281 100644 --- a/src/node/in_memory_ext.rs +++ b/src/node/in_memory_ext.rs @@ -1,3 +1,10 @@ +use crate::utils::Numeric; +use crate::{ + fork::{ForkDetails, ForkSource}, + namespaces::ResetRequest, + node::InMemoryNode, + utils::bytecode_to_factory_dep, +}; use anyhow::{anyhow, Context}; use std::convert::TryInto; use zksync_types::{ @@ -8,14 +15,6 @@ use zksync_types::{ use zksync_types::{AccountTreeId, Address, U256, U64}; use zksync_utils::u256_to_h256; -use crate::utils::Numeric; -use crate::{ - fork::{ForkDetails, ForkSource}, - namespaces::ResetRequest, - node::InMemoryNode, - utils::bytecode_to_factory_dep, -}; - type Result = anyhow::Result; /// The maximum number of [Snapshot]s to store. Each snapshot represents the node state @@ -350,6 +349,17 @@ impl InMemoryNo true }) } + + pub fn set_logging_enabled(&self, enable: bool) -> Result<()> { + let Some(observability) = &self.observability else { + anyhow::bail!("Node's logging is not set up"); + }; + if enable { + observability.enable_logging() + } else { + observability.disable_logging() + } + } } #[cfg(test)] @@ -485,7 +495,6 @@ mod tests { impersonation: Default::default(), rich_accounts: Default::default(), previous_states: Default::default(), - observability: None, }; let time = old_inner.time.clone(); let impersonation = old_inner.impersonation.clone(); @@ -496,6 +505,7 @@ mod tests { system_contracts_options: old_system_contracts_options, time, impersonation, + observability: None, }; let address = Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap(); diff --git a/src/observability.rs b/src/observability.rs index 2e1b79e3..4dd933bb 100644 --- a/src/observability.rs +++ b/src/observability.rs @@ -1,8 +1,8 @@ -use core::fmt; -use std::{fs::File, sync::Mutex}; - use clap::ValueEnum; +use core::fmt; use serde::{Deserialize, Serialize}; +use std::sync::{Arc, RwLock}; +use std::{fs::File, sync::Mutex}; use tracing_subscriber::{ filter::LevelFilter, layer::SubscriberExt, reload, util::SubscriberInitExt, EnvFilter, Registry, }; @@ -44,10 +44,12 @@ impl From for LevelFilter { } /// A sharable reference to the observability stack. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Observability { binary_names: Vec, - reload_handle: Option>, + reload_handle: reload::Handle, + /// Last directives used to reload the underlying `EnvFilter` instance. + last_directives: Arc>, } impl Observability { @@ -57,12 +59,12 @@ impl Observability { log_level_filter: LevelFilter, log_file: File, ) -> Result { - let joined_filter = binary_names + let directives = binary_names .iter() .map(|x| format!("{}={}", x, log_level_filter.to_string().to_lowercase())) .collect::>() .join(","); - let filter = Self::parse_filter(&joined_filter)?; + let filter = Self::parse_filter(&directives)?; let (filter, reload_handle) = reload::Layer::new(filter); let timer_format = @@ -95,22 +97,23 @@ impl Observability { Ok(Self { binary_names, - reload_handle: Some(reload_handle), + reload_handle, + last_directives: Arc::new(RwLock::new(directives)), }) } /// Set the log level for the binary. - pub fn set_log_level(&self, level: LogLevel) -> Result<(), anyhow::Error> { + pub fn set_log_level(&self, level: LogLevel) -> anyhow::Result<()> { let level = LevelFilter::from(level); - let new_filter = Self::parse_filter( - &self - .binary_names - .join(format!("={},", level.to_string().to_lowercase()).as_str()), - )?; - - if let Some(handle) = &self.reload_handle { - handle.modify(|filter| *filter = new_filter)?; - } + let directives = self + .binary_names + .join(&format!("={},", level.to_string().to_lowercase())); + let new_filter = Self::parse_filter(&directives)?; + self.reload_handle.reload(new_filter)?; + *self + .last_directives + .write() + .expect("Observability lock is poisoned") = directives; Ok(()) } @@ -120,12 +123,13 @@ impl Observability { /// * "my_crate=debug" /// * "my_crate::module=trace" /// * "my_crate=debug,other_crate=warn" - pub fn set_logging(&self, directive: &str) -> Result<(), anyhow::Error> { - let new_filter = Self::parse_filter(directive)?; - - if let Some(handle) = &self.reload_handle { - handle.modify(|filter| *filter = new_filter)?; - } + pub fn set_logging(&self, directives: String) -> Result<(), anyhow::Error> { + let new_filter = Self::parse_filter(&directives)?; + self.reload_handle.reload(new_filter)?; + *self + .last_directives + .write() + .expect("Observability lock is poisoned") = directives; Ok(()) } @@ -135,12 +139,32 @@ impl Observability { /// * "my_crate=debug" /// * "my_crate::module=trace" /// * "my_crate=debug,other_crate=warn" - fn parse_filter(directive: &str) -> Result { + fn parse_filter(directives: &str) -> Result { let mut filter = EnvFilter::from_default_env(); - for directive in directive.split(',') { + for directive in directives.split(',') { filter = filter.add_directive(directive.parse()?); } Ok(filter) } + + /// Enables logging with the latest used directives. + pub fn enable_logging(&self) -> Result<(), anyhow::Error> { + let last_directives = &*self + .last_directives + .read() + .expect("Observability lock is poisoned"); + let new_filter = Self::parse_filter(last_directives)?; + self.reload_handle.reload(new_filter)?; + + Ok(()) + } + + /// Disables all logging. + pub fn disable_logging(&self) -> Result<(), anyhow::Error> { + let new_filter = EnvFilter::new("off"); + self.reload_handle.reload(new_filter)?; + + Ok(()) + } }