From 8d8421ce0df7acfba86a3702a197683f30ce2b7c Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 6 Feb 2025 21:47:01 +1100 Subject: [PATCH 1/4] refactor: make zksync_utils thinner (#3568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ This PR removes heavy transitive dependencies from `zksync_utils` by: * Moving `ManagedTasks` into a separate crate (ideally needs to be removed in favor of node_framework but unfortunately still used by prover and contract-verifier) * Deleting custom-written retry logic for reqwest. The only place that was using was `RegionFetcher` from prover utils. I migrated it to `reqwest-retry` instead (Cargo.lock diff looks bigger than it actually is - almost every new crate is just some wasm-specific stuff that doesn't matter). ## Why ❔ Depending on `zksync_contracts`, `zksync_multivm` and other seemingly core crates pulls in a lot of transitive dependencies right now (`reqwest`, `sentry`, `opentelemetry`, `openssl` etc) ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- core/Cargo.lock | 19 +- core/Cargo.toml | 2 + core/bin/contract-verifier/Cargo.toml | 2 +- core/bin/contract-verifier/src/main.rs | 2 +- core/lib/task_management/Cargo.toml | 20 + .../src/lib.rs} | 4 +- core/lib/utils/Cargo.toml | 6 - core/lib/utils/src/http_with_retries.rs | 61 -- core/lib/utils/src/lib.rs | 2 - prover/Cargo.lock | 133 +++- prover/Cargo.toml | 9 +- prover/crates/bin/circuit_prover/Cargo.toml | 4 +- prover/crates/bin/circuit_prover/src/main.rs | 2 +- .../bin/proof_fri_compressor/Cargo.toml | 2 +- .../bin/proof_fri_compressor/src/main.rs | 2 +- .../crates/bin/prover_autoscaler/Cargo.toml | 2 +- .../crates/bin/prover_autoscaler/src/main.rs | 2 +- prover/crates/bin/prover_fri/Cargo.toml | 2 +- prover/crates/bin/prover_fri/src/main.rs | 2 +- .../crates/bin/prover_fri_gateway/Cargo.toml | 2 +- .../crates/bin/prover_fri_gateway/src/main.rs | 2 +- .../crates/bin/prover_job_monitor/Cargo.toml | 2 +- .../crates/bin/prover_job_monitor/src/main.rs | 2 +- .../crates/bin/witness_generator/Cargo.toml | 2 +- .../crates/bin/witness_generator/src/main.rs | 2 +- .../bin/witness_vector_generator/Cargo.toml | 2 +- .../bin/witness_vector_generator/src/main.rs | 2 +- prover/crates/lib/prover_fri_utils/Cargo.toml | 3 +- .../prover_fri_utils/src/region_fetcher.rs | 44 +- zkstack_cli/Cargo.lock | 574 +----------------- zkstack_cli/Cargo.toml | 2 + zkstack_cli/crates/zkstack/Cargo.toml | 4 +- 32 files changed, 230 insertions(+), 691 deletions(-) create mode 100644 core/lib/task_management/Cargo.toml rename core/lib/{utils/src/wait_for_tasks.rs => task_management/src/lib.rs} (97%) delete mode 100644 core/lib/utils/src/http_with_retries.rs diff --git a/core/Cargo.lock b/core/Cargo.lock index cd783f5f3928..5d1d2bd5bc55 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -11674,7 +11674,7 @@ dependencies = [ "zksync_core_leftovers", "zksync_dal", "zksync_queued_job_processor", - "zksync_utils", + "zksync_task_management", "zksync_vlog", ] @@ -12948,6 +12948,18 @@ dependencies = [ "zksync_basic_types", ] +[[package]] +name = "zksync_task_management" +version = "26.2.1-non-semver-compat" +dependencies = [ + "anyhow", + "futures 0.3.31", + "tokio", + "tracing", + "zksync_utils", + "zksync_vlog", +] + [[package]] name = "zksync_tee_prover" version = "26.2.1-non-semver-compat" @@ -13049,14 +13061,9 @@ version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", - "futures 0.3.31", "once_cell", - "reqwest 0.12.9", "serde_json", - "sha2 0.10.8", "tokio", - "tracing", - "zksync_vlog", ] [[package]] diff --git a/core/Cargo.toml b/core/Cargo.toml index f737007fbc65..c531ad2518bc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -76,6 +76,7 @@ members = [ "lib/snapshots_applier", "lib/crypto_primitives", "lib/external_price_api", + "lib/task_management", "lib/test_contracts", # Test infrastructure "tests/loadnext", @@ -296,6 +297,7 @@ zksync_utils = { version = "26.2.1-non-semver-compat", path = "lib/utils" } zksync_web3_decl = { version = "26.2.1-non-semver-compat", path = "lib/web3_decl" } zksync_crypto_primitives = { version = "26.2.1-non-semver-compat", path = "lib/crypto_primitives" } zksync_external_price_api = { version = "26.2.1-non-semver-compat", path = "lib/external_price_api" } +zksync_task_management = { version = "26.2.1-non-semver-compat", path = "lib/task_management" } # Framework and components zksync_node_framework = { version = "26.2.1-non-semver-compat", path = "node/node_framework" } diff --git a/core/bin/contract-verifier/Cargo.toml b/core/bin/contract-verifier/Cargo.toml index 5e9a9efc6e7e..f7293bab78e7 100644 --- a/core/bin/contract-verifier/Cargo.toml +++ b/core/bin/contract-verifier/Cargo.toml @@ -15,7 +15,7 @@ zksync_dal.workspace = true zksync_config = { workspace = true, features = ["observability_ext"] } zksync_contract_verifier_lib.workspace = true zksync_queued_job_processor.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_vlog.workspace = true zksync_core_leftovers.workspace = true diff --git a/core/bin/contract-verifier/src/main.rs b/core/bin/contract-verifier/src/main.rs index 93f23816c67d..0d7fea2519ec 100644 --- a/core/bin/contract-verifier/src/main.rs +++ b/core/bin/contract-verifier/src/main.rs @@ -8,7 +8,7 @@ use zksync_contract_verifier_lib::ContractVerifier; use zksync_core_leftovers::temp_config_store::{load_database_secrets, load_general_config}; use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_queued_job_processor::JobProcessor; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; #[derive(Debug, Parser)] diff --git a/core/lib/task_management/Cargo.toml b/core/lib/task_management/Cargo.toml new file mode 100644 index 000000000000..8e0e0fd460d3 --- /dev/null +++ b/core/lib/task_management/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "zksync_task_management" +description = "ZKsync task management" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +zksync_utils.workspace = true +zksync_vlog.workspace = true + +anyhow.workspace = true +futures.workspace = true +tokio = { workspace = true } +tracing.workspace = true diff --git a/core/lib/utils/src/wait_for_tasks.rs b/core/lib/task_management/src/lib.rs similarity index 97% rename from core/lib/utils/src/wait_for_tasks.rs rename to core/lib/task_management/src/lib.rs index 210d26484162..5212c60e59fe 100644 --- a/core/lib/utils/src/wait_for_tasks.rs +++ b/core/lib/task_management/src/lib.rs @@ -1,9 +1,9 @@ +// TODO: Migrate contract-verifier and provers to node_framework and get rid of this crate use std::time::Duration; use futures::future; use tokio::task::JoinHandle; - -use crate::panic_extractor::try_extract_panic_message; +use zksync_utils::panic_extractor::try_extract_panic_message; /// Container for fallible Tokio tasks with ability to track their shutdown. /// diff --git a/core/lib/utils/Cargo.toml b/core/lib/utils/Cargo.toml index fb08afc59b70..96cfdf714840 100644 --- a/core/lib/utils/Cargo.toml +++ b/core/lib/utils/Cargo.toml @@ -11,16 +11,10 @@ keywords.workspace = true categories.workspace = true [dependencies] -zksync_vlog.workspace = true - tokio = { workspace = true, features = ["time"] } -tracing.workspace = true anyhow.workspace = true -futures.workspace = true -reqwest = { workspace = true, features = ["blocking"] } serde_json.workspace = true once_cell.workspace = true -sha2.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/core/lib/utils/src/http_with_retries.rs b/core/lib/utils/src/http_with_retries.rs deleted file mode 100644 index 15973ee6b2a0..000000000000 --- a/core/lib/utils/src/http_with_retries.rs +++ /dev/null @@ -1,61 +0,0 @@ -use reqwest::{header::HeaderMap, Client, Error, Method, Response}; -use tokio::time::{sleep, Duration}; - -#[derive(Debug)] -pub enum HttpError { - ReqwestError(Error), - RetryExhausted(String), -} - -/// Method to send HTTP request with fixed number of retires with exponential back-offs. -pub async fn send_request_with_retries( - url: &str, - max_retries: usize, - method: Method, - headers: Option, - body: Option>, -) -> Result { - let mut retries = 0usize; - let mut delay = Duration::from_secs(1); - loop { - let result = send_request(url, method.clone(), headers.clone(), body.clone()).await; - match result { - Ok(response) if response.status().is_success() => return Ok(response), - Ok(response) => { - tracing::error!("Received non OK http response {:?}", response.status()) - } - Err(err) => tracing::error!("Error while sending http request {:?}", err), - } - if retries >= max_retries { - return Err(HttpError::RetryExhausted(format!( - "All {} http retires failed", - max_retries - ))); - } - retries += 1; - sleep(delay).await; - delay = delay.checked_mul(2).unwrap_or(Duration::MAX); - } -} - -async fn send_request( - url: &str, - method: Method, - headers: Option, - body: Option>, -) -> Result { - let client = Client::new(); - let mut request = client.request(method, url); - - if let Some(headers) = headers { - request = request.headers(headers); - } - - if let Some(body) = body { - request = request.body(body); - } - - let request = request.build()?; - let response = client.execute(request).await?; - Ok(response) -} diff --git a/core/lib/utils/src/lib.rs b/core/lib/utils/src/lib.rs index 85618a2e61ef..41ce0cf0fda9 100644 --- a/core/lib/utils/src/lib.rs +++ b/core/lib/utils/src/lib.rs @@ -1,6 +1,4 @@ //! Various helpers used in the ZKsync stack. pub mod env; -pub mod http_with_retries; pub mod panic_extractor; -pub mod wait_for_tasks; diff --git a/prover/Cargo.lock b/prover/Cargo.lock index c27a7fef2c97..f64f39d99274 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -2108,7 +2108,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.12.3", ] [[package]] @@ -3027,6 +3027,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -3486,7 +3489,7 @@ dependencies = [ "jsonptr", "k8s-openapi", "kube-client", - "parking_lot", + "parking_lot 0.12.3", "pin-project", "serde", "serde_json", @@ -4233,6 +4236,17 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -4240,7 +4254,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -4251,7 +4279,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -4570,7 +4598,7 @@ checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", - "parking_lot", + "parking_lot 0.12.3", "prometheus-client-derive-encode", ] @@ -4942,6 +4970,15 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -5112,6 +5149,28 @@ dependencies = [ "tower-service", ] +[[package]] +name = "reqwest-retry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178" +dependencies = [ + "anyhow", + "async-trait", + "futures 0.3.31", + "getrandom", + "http 1.2.0", + "hyper 1.5.2", + "parking_lot 0.11.2", + "reqwest 0.12.9", + "reqwest-middleware", + "retry-policies", + "thiserror 1.0.69", + "tokio", + "tracing", + "wasm-timer", +] + [[package]] name = "rescue_poseidon" version = "0.30.13" @@ -5137,6 +5196,15 @@ dependencies = [ "typemap_rev", ] +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -6685,7 +6753,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -7383,6 +7451,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures 0.3.31", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.76" @@ -7430,7 +7513,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall", + "redox_syscall 0.5.8", "wasite", ] @@ -8174,8 +8257,8 @@ dependencies = [ "zksync_prover_job_processor", "zksync_prover_keystore", "zksync_queued_job_processor", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] @@ -8620,8 +8703,8 @@ dependencies = [ "zksync_prover_interface", "zksync_prover_keystore", "zksync_queued_job_processor", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] @@ -8716,7 +8799,7 @@ dependencies = [ "vise", "zksync_config", "zksync_prover_job_monitor", - "zksync_utils", + "zksync_task_management", "zksync_vlog", ] @@ -8759,8 +8842,8 @@ dependencies = [ "zksync_prover_fri_utils", "zksync_prover_keystore", "zksync_queued_job_processor", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] @@ -8785,8 +8868,8 @@ dependencies = [ "zksync_object_store", "zksync_prover_dal", "zksync_prover_interface", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] @@ -8807,6 +8890,8 @@ dependencies = [ "anyhow", "regex", "reqwest 0.12.9", + "reqwest-middleware", + "reqwest-retry", "serde", "tracing", "vise", @@ -8815,7 +8900,6 @@ dependencies = [ "zksync_prover_dal", "zksync_prover_fri_types", "zksync_types", - "zksync_utils", ] [[package]] @@ -8852,8 +8936,8 @@ dependencies = [ "zksync_core_leftovers", "zksync_db_connection", "zksync_prover_dal", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] @@ -8937,6 +9021,18 @@ dependencies = [ "zksync_basic_types", ] +[[package]] +name = "zksync_task_management" +version = "26.2.1-non-semver-compat" +dependencies = [ + "anyhow", + "futures 0.3.31", + "tokio", + "tracing", + "zksync_utils", + "zksync_vlog", +] + [[package]] name = "zksync_types" version = "26.2.1-non-semver-compat" @@ -8975,14 +9071,9 @@ name = "zksync_utils" version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", - "futures 0.3.31", "once_cell", - "reqwest 0.12.9", "serde_json", - "sha2 0.10.8", "tokio", - "tracing", - "zksync_vlog", ] [[package]] @@ -9124,8 +9215,8 @@ dependencies = [ "zksync_prover_keystore", "zksync_queued_job_processor", "zksync_system_constants", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] @@ -9150,7 +9241,7 @@ dependencies = [ "zksync_prover_fri_utils", "zksync_prover_keystore", "zksync_queued_job_processor", + "zksync_task_management", "zksync_types", - "zksync_utils", "zksync_vlog", ] diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 7144b3821215..e410d876cb7e 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -45,6 +45,8 @@ queues = "1.1.0" rand = "0.8" regex = "1.10.4" reqwest = "0.12" +reqwest-middleware = "0.3.3" +reqwest-retry = "0.7.0" ring = "0.17.8" rustls = { version = "0.23.12", features = ["ring"] } serde = "1.0" @@ -74,9 +76,9 @@ fflonk = "=0.30.13" franklin-crypto = "=0.30.13" # GPU proving dependencies -proof-compression-gpu = { package = "proof-compression", version = "=0.152.11"} -fflonk-gpu = { package = "fflonk-cuda", version = "=0.152.11"} -wrapper_prover = { package = "zksync-wrapper-prover", version = "=0.152.11"} +proof-compression-gpu = { package = "proof-compression", version = "=0.152.11" } +fflonk-gpu = { package = "fflonk-cuda", version = "=0.152.11" } +wrapper_prover = { package = "zksync-wrapper-prover", version = "=0.152.11" } shivini = "=0.152.11" boojum-cuda = "=0.152.11" @@ -94,6 +96,7 @@ zksync_queued_job_processor = { path = "../core/lib/queued_job_processor" } zksync_system_constants = { path = "../core/lib/constants" } zksync_types = { path = "../core/lib/types" } zksync_utils = { path = "../core/lib/utils" } +zksync_task_management = { path = "../core/lib/task_management" } zksync_eth_client = { path = "../core/lib/eth_client" } zksync_contracts = { path = "../core/lib/contracts" } zksync_core_leftovers = { path = "../core/lib/zksync_core_leftovers" } diff --git a/prover/crates/bin/circuit_prover/Cargo.toml b/prover/crates/bin/circuit_prover/Cargo.toml index d1b87f0ce075..160908c34290 100644 --- a/prover/crates/bin/circuit_prover/Cargo.toml +++ b/prover/crates/bin/circuit_prover/Cargo.toml @@ -20,7 +20,7 @@ tracing.workspace = true bincode.workspace = true clap = { workspace = true, features = ["derive"] } -zksync_config = {workspace = true, features = ["observability_ext"]} +zksync_config = { workspace = true, features = ["observability_ext"] } zksync_object_store.workspace = true zksync_prover_dal.workspace = true zksync_prover_fri_types.workspace = true @@ -30,7 +30,7 @@ zksync_types.workspace = true zksync_prover_keystore = { workspace = true, features = ["gpu-light"] } zksync_env_config.workspace = true zksync_core_leftovers.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_circuit_prover_service.workspace = true zksync_prover_job_processor.workspace = true zksync_vlog.workspace = true diff --git a/prover/crates/bin/circuit_prover/src/main.rs b/prover/crates/bin/circuit_prover/src/main.rs index a445ceca3abe..aa4d34d6d13c 100644 --- a/prover/crates/bin/circuit_prover/src/main.rs +++ b/prover/crates/bin/circuit_prover/src/main.rs @@ -19,7 +19,7 @@ use zksync_object_store::{ObjectStore, ObjectStoreFactory}; use zksync_prover_dal::{ConnectionPool, Prover}; use zksync_prover_fri_types::PROVER_PROTOCOL_SEMANTIC_VERSION; use zksync_prover_keystore::keystore::Keystore; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; /// On most commodity hardware, WVG can take ~30 seconds to complete. diff --git a/prover/crates/bin/proof_fri_compressor/Cargo.toml b/prover/crates/bin/proof_fri_compressor/Cargo.toml index 008a9a73addc..6add16420e67 100644 --- a/prover/crates/bin/proof_fri_compressor/Cargo.toml +++ b/prover/crates/bin/proof_fri_compressor/Cargo.toml @@ -17,7 +17,7 @@ zksync_config = { workspace = true, features = ["observability_ext"] } zksync_env_config.workspace = true zksync_object_store.workspace = true zksync_prover_interface.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_core_leftovers.workspace = true zksync_prover_fri_types.workspace = true zksync_queued_job_processor.workspace = true diff --git a/prover/crates/bin/proof_fri_compressor/src/main.rs b/prover/crates/bin/proof_fri_compressor/src/main.rs index bb46c1e7cb75..5ed4a42ebc79 100644 --- a/prover/crates/bin/proof_fri_compressor/src/main.rs +++ b/prover/crates/bin/proof_fri_compressor/src/main.rs @@ -14,7 +14,7 @@ use zksync_prover_dal::{ConnectionPool, Prover, ProverDal}; use zksync_prover_fri_types::PROVER_PROTOCOL_SEMANTIC_VERSION; use zksync_prover_keystore::keystore::Keystore; use zksync_queued_job_processor::JobProcessor; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; use crate::{ diff --git a/prover/crates/bin/prover_autoscaler/Cargo.toml b/prover/crates/bin/prover_autoscaler/Cargo.toml index 4e66ecc2b0e3..b6e06ec5b2e6 100644 --- a/prover/crates/bin/prover_autoscaler/Cargo.toml +++ b/prover/crates/bin/prover_autoscaler/Cargo.toml @@ -11,7 +11,7 @@ categories.workspace = true [dependencies] zksync_vlog.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_config = { workspace = true, features = ["observability_ext"] } zksync_prover_job_monitor.workspace = true diff --git a/prover/crates/bin/prover_autoscaler/src/main.rs b/prover/crates/bin/prover_autoscaler/src/main.rs index 3baf3d13b2d6..402177da1ab7 100644 --- a/prover/crates/bin/prover_autoscaler/src/main.rs +++ b/prover/crates/bin/prover_autoscaler/src/main.rs @@ -14,7 +14,7 @@ use zksync_prover_autoscaler::{ k8s::{Scaler, Watcher}, task_wiring::TaskRunner, }; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; /// Represents the sequential number of the Prover Autoscaler type. diff --git a/prover/crates/bin/prover_fri/Cargo.toml b/prover/crates/bin/prover_fri/Cargo.toml index f9938ad45ba3..791b08364333 100644 --- a/prover/crates/bin/prover_fri/Cargo.toml +++ b/prover/crates/bin/prover_fri/Cargo.toml @@ -21,7 +21,7 @@ zksync_queued_job_processor.workspace = true zksync_prover_fri_utils.workspace = true zksync_core_leftovers.workspace = true zksync_prover_fri_types.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_prover_keystore.workspace = true shivini = { workspace = true, optional = true, features = [ "circuit_definitions", diff --git a/prover/crates/bin/prover_fri/src/main.rs b/prover/crates/bin/prover_fri/src/main.rs index d464cb630be2..afab465ec03f 100644 --- a/prover/crates/bin/prover_fri/src/main.rs +++ b/prover/crates/bin/prover_fri/src/main.rs @@ -21,11 +21,11 @@ use zksync_prover_fri_utils::{ region_fetcher::{RegionFetcher, Zone}, }; use zksync_queued_job_processor::JobProcessor; +use zksync_task_management::ManagedTasks; use zksync_types::{ basic_fri_types::CircuitIdRoundTuple, prover_dal::{GpuProverInstanceStatus, SocketAddress}, }; -use zksync_utils::wait_for_tasks::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; mod gpu_prover_availability_checker; diff --git a/prover/crates/bin/prover_fri_gateway/Cargo.toml b/prover/crates/bin/prover_fri_gateway/Cargo.toml index 8d116c4219d6..42cb970306b6 100644 --- a/prover/crates/bin/prover_fri_gateway/Cargo.toml +++ b/prover/crates/bin/prover_fri_gateway/Cargo.toml @@ -18,7 +18,7 @@ zksync_env_config.workspace = true zksync_core_leftovers.workspace = true zksync_object_store.workspace = true zksync_prover_interface.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_vlog.workspace = true anyhow.workspace = true diff --git a/prover/crates/bin/prover_fri_gateway/src/main.rs b/prover/crates/bin/prover_fri_gateway/src/main.rs index ed0574e7fba1..c69f6775a143 100644 --- a/prover/crates/bin/prover_fri_gateway/src/main.rs +++ b/prover/crates/bin/prover_fri_gateway/src/main.rs @@ -10,7 +10,7 @@ use zksync_core_leftovers::temp_config_store::{load_database_secrets, load_gener use zksync_env_config::object_store::ProverObjectStoreConfig; use zksync_object_store::ObjectStoreFactory; use zksync_prover_dal::{ConnectionPool, Prover}; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; mod client; diff --git a/prover/crates/bin/prover_job_monitor/Cargo.toml b/prover/crates/bin/prover_job_monitor/Cargo.toml index a4bf8765a946..3449ea4cd071 100644 --- a/prover/crates/bin/prover_job_monitor/Cargo.toml +++ b/prover/crates/bin/prover_job_monitor/Cargo.toml @@ -13,7 +13,7 @@ categories.workspace = true zksync_core_leftovers.workspace = true zksync_vlog.workspace = true zksync_prover_dal.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_types.workspace = true zksync_config = { workspace = true, features = ["observability_ext"] } zksync_db_connection.workspace = true diff --git a/prover/crates/bin/prover_job_monitor/src/main.rs b/prover/crates/bin/prover_job_monitor/src/main.rs index 8511135225a6..896c00850f37 100644 --- a/prover/crates/bin/prover_job_monitor/src/main.rs +++ b/prover/crates/bin/prover_job_monitor/src/main.rs @@ -22,7 +22,7 @@ use zksync_prover_job_monitor::{ task_wiring::TaskRunner, witness_job_queuer::WitnessJobQueuer, }; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; #[derive(Debug, Parser)] diff --git a/prover/crates/bin/witness_generator/Cargo.toml b/prover/crates/bin/witness_generator/Cargo.toml index bb6a44e7eb33..fc1d8bba4b02 100644 --- a/prover/crates/bin/witness_generator/Cargo.toml +++ b/prover/crates/bin/witness_generator/Cargo.toml @@ -21,7 +21,7 @@ zksync_queued_job_processor.workspace = true zksync_multivm.workspace = true zksync_object_store.workspace = true zksync_types.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_prover_keystore.workspace = true zksync_prover_fri_types.workspace = true zksync_prover_fri_utils.workspace = true diff --git a/prover/crates/bin/witness_generator/src/main.rs b/prover/crates/bin/witness_generator/src/main.rs index b9006341d03c..db51a58bc06d 100644 --- a/prover/crates/bin/witness_generator/src/main.rs +++ b/prover/crates/bin/witness_generator/src/main.rs @@ -16,8 +16,8 @@ use zksync_prover_dal::{ConnectionPool, Prover, ProverDal}; use zksync_prover_fri_types::PROVER_PROTOCOL_SEMANTIC_VERSION; use zksync_prover_keystore::keystore::Keystore; use zksync_queued_job_processor::JobProcessor; +use zksync_task_management::ManagedTasks; use zksync_types::{basic_fri_types::AggregationRound, protocol_version::ProtocolSemanticVersion}; -use zksync_utils::wait_for_tasks::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; use zksync_witness_generator::{ metrics::SERVER_METRICS, diff --git a/prover/crates/bin/witness_vector_generator/Cargo.toml b/prover/crates/bin/witness_vector_generator/Cargo.toml index e8386c8090a3..91197c3cc8d8 100644 --- a/prover/crates/bin/witness_vector_generator/Cargo.toml +++ b/prover/crates/bin/witness_vector_generator/Cargo.toml @@ -17,7 +17,7 @@ zksync_config = { workspace = true, features = ["observability_ext"] } zksync_env_config.workspace = true zksync_object_store.workspace = true zksync_prover_fri_utils.workspace = true -zksync_utils.workspace = true +zksync_task_management.workspace = true zksync_prover_fri_types.workspace = true zksync_core_leftovers.workspace = true zksync_queued_job_processor.workspace = true diff --git a/prover/crates/bin/witness_vector_generator/src/main.rs b/prover/crates/bin/witness_vector_generator/src/main.rs index 17ac3bd6fc9f..86f02756321a 100644 --- a/prover/crates/bin/witness_vector_generator/src/main.rs +++ b/prover/crates/bin/witness_vector_generator/src/main.rs @@ -14,7 +14,7 @@ use zksync_prover_fri_types::PROVER_PROTOCOL_SEMANTIC_VERSION; use zksync_prover_fri_utils::{get_all_circuit_id_round_tuples_for, region_fetcher::RegionFetcher}; use zksync_prover_keystore::keystore::Keystore; use zksync_queued_job_processor::JobProcessor; -use zksync_utils::wait_for_tasks::ManagedTasks; +use zksync_task_management::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; use crate::generator::WitnessVectorGenerator; diff --git a/prover/crates/lib/prover_fri_utils/Cargo.toml b/prover/crates/lib/prover_fri_utils/Cargo.toml index 06b3af54cd3b..8192463f729d 100644 --- a/prover/crates/lib/prover_fri_utils/Cargo.toml +++ b/prover/crates/lib/prover_fri_utils/Cargo.toml @@ -18,10 +18,11 @@ zksync_config.workspace = true zksync_types.workspace = true zksync_prover_fri_types.workspace = true zksync_prover_dal.workspace = true -zksync_utils.workspace = true tracing.workspace = true serde = { workspace = true, features = ["derive"] } reqwest = { workspace = true, features = ["blocking"] } +reqwest-middleware.workspace = true +reqwest-retry.workspace = true regex.workspace = true anyhow.workspace = true diff --git a/prover/crates/lib/prover_fri_utils/src/region_fetcher.rs b/prover/crates/lib/prover_fri_utils/src/region_fetcher.rs index c3a137ecfc6b..937f5bfda991 100644 --- a/prover/crates/lib/prover_fri_utils/src/region_fetcher.rs +++ b/prover/crates/lib/prover_fri_utils/src/region_fetcher.rs @@ -1,16 +1,16 @@ use core::fmt; +use std::time::Duration; use anyhow::Context; use regex::Regex; -use reqwest::{ - header::{HeaderMap, HeaderValue}, - Method, -}; +use reqwest::header::{HeaderMap, HeaderValue}; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; use zksync_config::configs::fri_prover::CloudConnectionMode; -use zksync_utils::http_with_retries::send_request_with_retries; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct RegionFetcher { + gcp_zone_fetcher: GcpZoneFetcher, cloud_type: CloudConnectionMode, zone_url: String, } @@ -18,6 +18,7 @@ pub struct RegionFetcher { impl RegionFetcher { pub fn new(cloud_type: CloudConnectionMode, zone_url: String) -> Self { Self { + gcp_zone_fetcher: GcpZoneFetcher::new(), cloud_type, zone_url, } @@ -25,7 +26,7 @@ impl RegionFetcher { pub async fn get_zone(&self) -> anyhow::Result { match self.cloud_type { - CloudConnectionMode::GCP => GcpZoneFetcher::get_zone(&self.zone_url).await, + CloudConnectionMode::GCP => self.gcp_zone_fetcher.get_zone(&self.zone_url).await, CloudConnectionMode::Local => Ok(Zone("local".to_string())), } } @@ -46,21 +47,38 @@ impl Zone { } } -#[derive(Debug, Clone, Copy)] -struct GcpZoneFetcher; +#[derive(Debug, Clone)] +struct GcpZoneFetcher { + client: ClientWithMiddleware, +} impl GcpZoneFetcher { - pub async fn get_zone(zone_url: &str) -> anyhow::Result { - let data = Self::fetch_from_url(zone_url) + pub fn new() -> Self { + let retry_policy = ExponentialBackoff::builder() + .base(2) + .retry_bounds(Duration::from_secs(1), Duration::MAX) + .build_with_max_retries(5); + let client = ClientBuilder::new(reqwest::Client::new()) + .with( + RetryTransientMiddleware::new_with_policy(retry_policy) + .with_retry_log_level(tracing::Level::ERROR), + ) + .build(); + Self { client } + } + + pub async fn get_zone(&self, zone_url: &str) -> anyhow::Result { + let data = self + .fetch_from_url(zone_url) .await .context("fetch_from_url()")?; Self::parse_zone(&data).context("parse_zone") } - async fn fetch_from_url(url: &str) -> anyhow::Result { + async fn fetch_from_url(&self, url: &str) -> anyhow::Result { let mut headers = HeaderMap::new(); headers.insert("Metadata-Flavor", HeaderValue::from_static("Google")); - let response = send_request_with_retries(url, 5, Method::GET, Some(headers), None).await; + let response = self.client.get(url).headers(headers).send().await; response .map_err(|err| anyhow::anyhow!("Failed fetching response from url: {url}: {err:?}"))? .text() diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index 58bd4ce4f1ee..f13f1aacd81b 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -159,28 +159,6 @@ dependencies = [ "term", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", -] - [[package]] name = "async-trait" version = "0.1.83" @@ -261,53 +239,6 @@ dependencies = [ "paste", ] -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 1.0.2", - "tower 0.5.1", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -1117,16 +1048,6 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "serde", - "uuid 1.11.0", -] - [[package]] name = "der" version = "0.7.9" @@ -1807,18 +1728,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "findshlibs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" -dependencies = [ - "cc", - "lazy_static", - "libc", - "winapi", -] - [[package]] name = "fixed-hash" version = "0.8.0" @@ -2144,7 +2053,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2163,7 +2072,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.6.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2180,12 +2089,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -2265,17 +2168,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "http" version = "0.2.12" @@ -2397,7 +2289,6 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2437,32 +2328,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper 1.5.1", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.31", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -2710,16 +2575,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.6.0" @@ -2941,7 +2796,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tower 0.4.13", + "tower", "tracing", "url", ] @@ -3204,12 +3059,6 @@ dependencies = [ "logos-codegen", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -3219,12 +3068,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "md-5" version = "0.10.6" @@ -3582,104 +3425,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c365a63eec4f55b7efeceb724f1336f26a9cf3427b70e59e2cd2a5b947fba96" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror", -] - -[[package]] -name = "opentelemetry-appender-tracing" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84de945cb3a6f1e0d6317cbd998bbd0519ab00f4b790db67e0ff4fdcf7cedb6" -dependencies = [ - "opentelemetry", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "opentelemetry-http" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad31e9de44ee3538fb9d64fe3376c1362f406162434609e79aea2a41a0af78ab" -dependencies = [ - "async-trait", - "bytes", - "http 1.1.0", - "opentelemetry", - "reqwest 0.12.9", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b925a602ffb916fb7421276b86756027b37ee708f9dce2dbdcc51739f07e727" -dependencies = [ - "async-trait", - "futures-core", - "http 1.1.0", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost 0.13.3", - "reqwest 0.12.9", - "thiserror", - "tokio", - "tonic", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee9f20bff9c984511a02f082dc8ede839e4a9bf15cc2487c8d6fea5ad850d9" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost 0.13.3", - "tonic", -] - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cefe0543875379e47eb5f1e68ff83f45cc41366a92dfd0d073d513bf68e9a05" - -[[package]] -name = "opentelemetry_sdk" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692eac490ec80f24a17828d49b40b60f5aeaccdfe6a503f939713afd22bc28df" -dependencies = [ - "async-trait", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "once_cell", - "opentelemetry", - "percent-encoding", - "rand", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -3844,7 +3589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap", ] [[package]] @@ -4094,17 +3839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.6", -] - -[[package]] -name = "prost" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" -dependencies = [ - "bytes", - "prost-derive 0.13.3", + "prost-derive", ] [[package]] @@ -4121,7 +3856,7 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.12.6", + "prost", "prost-types", "regex", "syn 2.0.89", @@ -4141,19 +3876,6 @@ dependencies = [ "syn 2.0.89", ] -[[package]] -name = "prost-derive" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" -dependencies = [ - "anyhow", - "itertools 0.13.0", - "proc-macro2", - "quote", - "syn 2.0.89", -] - [[package]] name = "prost-reflect" version = "0.12.0" @@ -4164,7 +3886,7 @@ dependencies = [ "logos", "miette", "once_cell", - "prost 0.12.6", + "prost", "prost-types", "serde", "serde-value", @@ -4176,7 +3898,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.6", + "prost", ] [[package]] @@ -4187,7 +3909,7 @@ checksum = "00bb76c5f6221de491fe2c8f39b106330bbd9762c6511119c07940e10eb9ff11" dependencies = [ "bytes", "miette", - "prost 0.12.6", + "prost", "prost-reflect", "prost-types", "protox-parse", @@ -4369,12 +4091,10 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.31", "hyper-rustls 0.24.2", - "hyper-tls 0.5.0", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4386,7 +4106,6 @@ dependencies = [ "sync_wrapper 0.1.2", "system-configuration 0.5.1", "tokio", - "tokio-native-tls", "tokio-rustls 0.24.1", "tower-service", "url", @@ -4415,7 +4134,7 @@ dependencies = [ "http-body-util", "hyper 1.5.1", "hyper-rustls 0.27.3", - "hyper-tls 0.6.0", + "hyper-tls", "hyper-util", "ipnet", "js-sys", @@ -4869,114 +4588,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -[[package]] -name = "sentry" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce4b57f1b521f674df7a1d200be8ff5d74e3712020ee25b553146657b5377d5" -dependencies = [ - "httpdate", - "native-tls", - "reqwest 0.11.27", - "sentry-backtrace", - "sentry-contexts", - "sentry-core", - "sentry-debug-images", - "sentry-panic", - "sentry-tracing", - "tokio", - "ureq", -] - -[[package]] -name = "sentry-backtrace" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cc8d4e04a73de8f718dc703943666d03f25d3e9e4d0fb271ca0b8c76dfa00e" -dependencies = [ - "backtrace", - "once_cell", - "regex", - "sentry-core", -] - -[[package]] -name = "sentry-contexts" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6436c1bad22cdeb02179ea8ef116ffc217797c028927def303bc593d9320c0d1" -dependencies = [ - "hostname", - "libc", - "os_info", - "rustc_version", - "sentry-core", - "uname", -] - -[[package]] -name = "sentry-core" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901f761681f97db3db836ef9e094acdd8756c40215326c194201941947164ef1" -dependencies = [ - "once_cell", - "rand", - "sentry-types", - "serde", - "serde_json", -] - -[[package]] -name = "sentry-debug-images" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdb263e73d22f39946f6022ed455b7561b22ff5553aca9be3c6a047fa39c328" -dependencies = [ - "findshlibs", - "once_cell", - "sentry-core", -] - -[[package]] -name = "sentry-panic" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fbf1c163f8b6a9d05912e1b272afa27c652e8b47ea60cb9a57ad5e481eea99" -dependencies = [ - "sentry-backtrace", - "sentry-core", -] - -[[package]] -name = "sentry-tracing" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82eabcab0a047040befd44599a1da73d3adb228ff53b5ed9795ae04535577704" -dependencies = [ - "sentry-backtrace", - "sentry-core", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "sentry-types" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da956cca56e0101998c8688bc65ce1a96f00673a0e58e663664023d4c7911e82" -dependencies = [ - "debugid", - "hex", - "rand", - "serde", - "serde_json", - "thiserror", - "time", - "url", - "uuid 1.11.0", -] - [[package]] name = "serde" version = "1.0.215" @@ -5069,7 +4680,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.6.0", + "indexmap", "itoa", "ryu", "serde", @@ -5324,7 +4935,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.6.0", + "indexmap", "log", "memchr", "once_cell", @@ -5499,7 +5110,7 @@ dependencies = [ "enum_dispatch", "fancy-regex", "getrandom", - "indexmap 2.6.0", + "indexmap", "itertools 0.13.0", "lazy-regex", "nohash-hasher", @@ -5528,7 +5139,7 @@ dependencies = [ "ahash", "enum_dispatch", "fancy-regex", - "indexmap 2.6.0", + "indexmap", "itertools 0.13.0", "nohash-hasher", "pretty_assertions", @@ -6041,43 +5652,13 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] -[[package]] -name = "tonic" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.22.1", - "bytes", - "h2 0.4.7", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.5.1", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "prost 0.13.3", - "socket2", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.4.13" @@ -6086,32 +5667,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util", "tower-layer", "tower-service", "tracing", ] -[[package]] -name = "tower" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 0.1.2", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-layer" version = "0.3.3" @@ -6178,34 +5740,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9784ed4da7d921bc8df6963f8c80a0e4ce34ba6ba76668acadd3edbd985ff3b" -dependencies = [ - "js-sys", - "once_cell", - "opentelemetry", - "opentelemetry_sdk", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -6216,16 +5750,12 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", - "time", "tracing", "tracing-core", "tracing-log", - "tracing-serde", ] [[package]] @@ -6272,15 +5802,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "uname" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" -dependencies = [ - "libc", -] - [[package]] name = "unarray" version = "0.1.4" @@ -6368,19 +5889,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" -dependencies = [ - "base64 0.22.1", - "log", - "native-tls", - "once_cell", - "url", -] - [[package]] name = "url" version = "2.5.4" @@ -6434,7 +5942,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", - "serde", ] [[package]] @@ -6469,19 +5976,6 @@ dependencies = [ "vise-macros", ] -[[package]] -name = "vise-exporter" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671d3b894d5d0849f0a597f56bf071f42d4f2a1cbcf2f78ca21f870ab7c0cc2b" -dependencies = [ - "hyper 0.14.31", - "once_cell", - "tokio", - "tracing", - "vise", -] - [[package]] name = "vise-macros" version = "0.2.0" @@ -7095,7 +6589,7 @@ dependencies = [ "futures", "human-panic", "lazy_static", - "prost 0.12.6", + "prost", "rand", "reqwest 0.12.9", "secrecy", @@ -7283,7 +6777,7 @@ dependencies = [ "bit-vec", "hex", "num-bigint", - "prost 0.12.6", + "prost", "rand", "serde", "thiserror", @@ -7382,7 +6876,7 @@ dependencies = [ "anyhow", "bit-vec", "once_cell", - "prost 0.12.6", + "prost", "prost-reflect", "quick-protobuf", "rand", @@ -7435,7 +6929,7 @@ dependencies = [ "num", "num_enum", "once_cell", - "prost 0.12.6", + "prost", "rlp", "serde", "serde_json", @@ -7457,39 +6951,9 @@ name = "zksync_utils" version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", - "futures", "once_cell", - "reqwest 0.12.9", - "serde_json", - "sha2", - "tokio", - "tracing", - "zksync_vlog", -] - -[[package]] -name = "zksync_vlog" -version = "26.2.1-non-semver-compat" -dependencies = [ - "anyhow", - "chrono", - "opentelemetry", - "opentelemetry-appender-tracing", - "opentelemetry-otlp", - "opentelemetry-semantic-conventions", - "opentelemetry_sdk", - "sentry", - "serde", "serde_json", - "thiserror", - "time", "tokio", - "tracing", - "tracing-opentelemetry", - "tracing-subscriber", - "url", - "vise", - "vise-exporter", ] [[package]] diff --git a/zkstack_cli/Cargo.toml b/zkstack_cli/Cargo.toml index 5ef8338dca09..cd48ec81bacc 100644 --- a/zkstack_cli/Cargo.toml +++ b/zkstack_cli/Cargo.toml @@ -73,3 +73,5 @@ xshell = "0.2.6" clap-markdown = "0.1.4" secrecy = "0.8.0" async-trait = "0.1.68" +sqruff-lib = "0.19.0" +reqwest = { version = "0.12.8", features = ["blocking"] } diff --git a/zkstack_cli/crates/zkstack/Cargo.toml b/zkstack_cli/crates/zkstack/Cargo.toml index f39b5f5ea085..cceb6419d607 100644 --- a/zkstack_cli/crates/zkstack/Cargo.toml +++ b/zkstack_cli/crates/zkstack/Cargo.toml @@ -30,7 +30,7 @@ serde_json.workspace = true serde_yaml.workspace = true slugify-rs.workspace = true strum.workspace = true -sqruff-lib = "0.19.0" +sqruff-lib.workspace = true thiserror.workspace = true tokio.workspace = true toml.workspace = true @@ -48,7 +48,7 @@ zksync_system_constants.workspace = true zksync_eth_client.workspace = true zksync_contracts.workspace = true prost.workspace = true -reqwest = "0.12.8" +reqwest.workspace = true [dev-dependencies] rand.workspace = true From bda1b25c67aed2d5cb7f7a1cc6eb0afc533c83b7 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Thu, 6 Feb 2025 15:54:35 +0400 Subject: [PATCH 2/4] fix(api): Improve estimation for gas_per_pubdata_limit (#3475) Fixes the `gas_per_pubdata_limit` field calculation. Right now, we provide the value we get from deriving the existing batch input, which is most likely lower than limit provided in fee estimation request. Given that we scale the overall fee, it should be safe to scale the limit as well, as long as we don't go beyond the limit supplied in the tx. Providing the limit as-is without scaling can be very flaky: transaction risks to be left in the mempool over minor `gas_per_pubdata` changes. --- core/node/api_server/src/tx_sender/gas_estimation.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/node/api_server/src/tx_sender/gas_estimation.rs b/core/node/api_server/src/tx_sender/gas_estimation.rs index b4a05a0756b6..b370e419eab2 100644 --- a/core/node/api_server/src/tx_sender/gas_estimation.rs +++ b/core/node/api_server/src/tx_sender/gas_estimation.rs @@ -530,11 +530,18 @@ impl<'a> GasEstimator<'a> { gas_per_pubdata_byte = self.gas_per_pubdata_byte ); + // Given that we scale overall fee, we should also scale the limit for gas per pubdata price that user agrees to. + // However, we should not exceed the limit that was provided by the user in the initial request. + let gas_per_pubdata_limit = std::cmp::min( + ((self.gas_per_pubdata_byte as f64 * estimated_fee_scale_factor) as u64).into(), + self.transaction.gas_per_pubdata_byte_limit(), + ); + Ok(Fee { max_fee_per_gas: self.base_fee.into(), max_priority_fee_per_gas: 0u32.into(), gas_limit: full_gas_limit.into(), - gas_per_pubdata_limit: self.gas_per_pubdata_byte.into(), + gas_per_pubdata_limit, }) } } From a3528522988093fbd2697b8fc35eb24f00166699 Mon Sep 17 00:00:00 2001 From: Artem Fomiuk <88630083+Artemka374@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:46:37 +0200 Subject: [PATCH 3/4] feat: update FFLONK protocol version (#3572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ ## Why ❔ ## Checklist - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- core/lib/basic_types/src/protocol_version.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/basic_types/src/protocol_version.rs b/core/lib/basic_types/src/protocol_version.rs index 5d896040f760..b09e94e42ab2 100644 --- a/core/lib/basic_types/src/protocol_version.rs +++ b/core/lib/basic_types/src/protocol_version.rs @@ -152,7 +152,7 @@ impl ProtocolVersionId { } pub fn is_pre_fflonk(&self) -> bool { - self < &Self::Version28 + self < &Self::Version27 } pub fn is_1_4_0(&self) -> bool { From 4179711c82c210e0a1236c37a2a97fb9311ef820 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 6 Feb 2025 18:11:11 +0200 Subject: [PATCH 4/4] fix(api): Change `contractAddress` assignment for transaction receipts (#3452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Changes how `contractAddress` is assigned for transaction receipts. With these changes, it is assigned for EVM deployment transactions (ones with `to == None`) and EraVM deployments (i.e., calls to `ContractDeployer.{create, create2, createAccount, create2Account}`) regardless of whether the transaction succeeds. ## Why ❔ Improves EVM compatibility. ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. - [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- core/lib/contract_verifier/src/lib.rs | 2 +- ...b2aa3e98338e857477d879ae24ebe16c0e0c.json} | 32 +- ...ba6224045c4820ec9c9e4ec627a8401341b9f.json | 36 ++ core/lib/dal/src/events_web3_dal.rs | 45 +- .../lib/dal/src/models/storage_transaction.rs | 32 +- core/lib/dal/src/transactions_web3_dal.rs | 61 +-- core/lib/multivm/src/utils/deduplicator.rs | 18 +- core/lib/multivm/src/versions/shadow/tests.rs | 10 + .../src/versions/testonly/default_aa.rs | 78 ++- core/lib/multivm/src/versions/testonly/mod.rs | 25 +- .../src/versions/testonly/simple_execution.rs | 44 +- .../src/versions/vm_fast/tests/default_aa.rs | 10 +- .../vm_fast/tests/simple_execution.rs | 9 +- .../versions/vm_latest/tests/default_aa.rs | 7 +- .../vm_latest/tests/simple_execution.rs | 9 +- .../contracts/custom-account/permissive.sol | 96 ++++ core/lib/test_contracts/src/contracts.rs | 11 + core/lib/types/src/tx/execute.rs | 154 +++++- core/lib/types/src/utils.rs | 46 +- .../api_server/src/execution_sandbox/tests.rs | 9 +- core/node/api_server/src/testonly.rs | 469 +++++++++++++---- .../api_server/src/tx_sender/tests/call.rs | 33 +- .../src/tx_sender/tests/gas_estimation.rs | 38 +- .../api_server/src/tx_sender/tests/send_tx.rs | 14 +- core/node/api_server/src/utils.rs | 473 +++++++++++++++++- .../api_server/src/web3/namespaces/eth.rs | 7 +- core/node/api_server/src/web3/tests/debug.rs | 2 +- .../node/api_server/src/web3/tests/filters.rs | 2 +- core/node/api_server/src/web3/tests/mod.rs | 81 +-- .../api_server/src/web3/tests/snapshots.rs | 2 +- core/node/api_server/src/web3/tests/ws.rs | 2 +- core/node/node_sync/src/tests.rs | 9 +- .../state_keeper/src/executor/tests/tester.rs | 3 +- core/node/state_keeper/src/io/persistence.rs | 6 +- .../io/seal_logic/l2_block_seal_subtasks.rs | 11 +- .../state_keeper/src/io/seal_logic/mod.rs | 16 +- core/node/state_keeper/src/tests/mod.rs | 46 +- core/node/test_utils/src/lib.rs | 44 +- 38 files changed, 1592 insertions(+), 400 deletions(-) rename core/lib/dal/.sqlx/{query-bba037e1fcffc4415afe3016ff266d19f7ba92c40566e1d098c435da41e95274.json => query-02972d40481cee9757d1a6d6eb65b2aa3e98338e857477d879ae24ebe16c0e0c.json} (50%) create mode 100644 core/lib/dal/.sqlx/query-47d94abb258de733fa65eaae0f5ba6224045c4820ec9c9e4ec627a8401341b9f.json create mode 100644 core/lib/test_contracts/contracts/custom-account/permissive.sol diff --git a/core/lib/contract_verifier/src/lib.rs b/core/lib/contract_verifier/src/lib.rs index 864c7b747d43..092c8f497724 100644 --- a/core/lib/contract_verifier/src/lib.rs +++ b/core/lib/contract_verifier/src/lib.rs @@ -484,7 +484,7 @@ impl ContractVerifier { if selector == create_acc.short_signature() || selector == create2_acc.short_signature() => { - let tokens = create + let tokens = create_acc .decode_input(token_data) .context("failed to decode `createAccount` / `create2Account` input")?; // Constructor arguments are in the third parameter. diff --git a/core/lib/dal/.sqlx/query-bba037e1fcffc4415afe3016ff266d19f7ba92c40566e1d098c435da41e95274.json b/core/lib/dal/.sqlx/query-02972d40481cee9757d1a6d6eb65b2aa3e98338e857477d879ae24ebe16c0e0c.json similarity index 50% rename from core/lib/dal/.sqlx/query-bba037e1fcffc4415afe3016ff266d19f7ba92c40566e1d098c435da41e95274.json rename to core/lib/dal/.sqlx/query-02972d40481cee9757d1a6d6eb65b2aa3e98338e857477d879ae24ebe16c0e0c.json index a72b621dae59..cf6c5cb685b2 100644 --- a/core/lib/dal/.sqlx/query-bba037e1fcffc4415afe3016ff266d19f7ba92c40566e1d098c435da41e95274.json +++ b/core/lib/dal/.sqlx/query-02972d40481cee9757d1a6d6eb65b2aa3e98338e857477d879ae24ebe16c0e0c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n WITH\n events AS (\n SELECT DISTINCT\n ON (events.tx_hash) *\n FROM\n events\n WHERE\n events.address = $1\n AND events.topic1 = $2\n AND events.tx_hash = ANY($3)\n ORDER BY\n events.tx_hash,\n events.event_index_in_tx DESC\n )\n \n SELECT\n transactions.hash AS tx_hash,\n transactions.index_in_block,\n transactions.l1_batch_tx_index,\n transactions.miniblock_number AS \"block_number!\",\n transactions.error,\n transactions.effective_gas_price,\n transactions.initiator_address,\n transactions.data -> 'to' AS \"transfer_to?\",\n transactions.data -> 'contractAddress' AS \"execute_contract_address?\",\n transactions.tx_format AS \"tx_format?\",\n transactions.refunded_gas,\n transactions.gas_limit,\n miniblocks.hash AS \"block_hash\",\n miniblocks.l1_batch_number AS \"l1_batch_number?\",\n events.topic4 AS \"contract_address?\",\n miniblocks.timestamp AS \"block_timestamp?\"\n FROM\n transactions\n JOIN miniblocks ON miniblocks.number = transactions.miniblock_number\n LEFT JOIN events ON events.tx_hash = transactions.hash\n WHERE\n transactions.hash = ANY($3)\n AND transactions.data != '{}'::jsonb\n ", + "query": "\n SELECT\n transactions.hash AS tx_hash,\n transactions.index_in_block,\n transactions.l1_batch_tx_index,\n transactions.miniblock_number AS \"block_number!\",\n transactions.error,\n transactions.effective_gas_price,\n transactions.initiator_address,\n transactions.data -> 'to' AS \"transfer_to?\",\n transactions.data -> 'contractAddress' AS \"execute_contract_address?\",\n transactions.data -> 'calldata' AS \"calldata\",\n transactions.tx_format AS \"tx_format?\",\n transactions.refunded_gas,\n transactions.gas_limit,\n transactions.nonce,\n miniblocks.hash AS \"block_hash\",\n miniblocks.l1_batch_number AS \"l1_batch_number?\",\n miniblocks.timestamp AS \"block_timestamp?\"\n FROM\n transactions\n JOIN miniblocks ON miniblocks.number = transactions.miniblock_number\n WHERE\n transactions.hash = ANY($1)\n AND transactions.data != '{}'::jsonb\n ", "describe": { "columns": [ { @@ -50,44 +50,47 @@ }, { "ordinal": 9, + "name": "calldata", + "type_info": "Jsonb" + }, + { + "ordinal": 10, "name": "tx_format?", "type_info": "Int4" }, { - "ordinal": 10, + "ordinal": 11, "name": "refunded_gas", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "gas_limit", "type_info": "Numeric" }, - { - "ordinal": 12, - "name": "block_hash", - "type_info": "Bytea" - }, { "ordinal": 13, - "name": "l1_batch_number?", + "name": "nonce", "type_info": "Int8" }, { "ordinal": 14, - "name": "contract_address?", + "name": "block_hash", "type_info": "Bytea" }, { "ordinal": 15, + "name": "l1_batch_number?", + "type_info": "Int8" + }, + { + "ordinal": 16, "name": "block_timestamp?", "type_info": "Int8" } ], "parameters": { "Left": [ - "Bytea", - "Bytea", "ByteaArray" ] }, @@ -101,14 +104,15 @@ false, null, null, + null, true, false, true, - false, true, + false, true, false ] }, - "hash": "bba037e1fcffc4415afe3016ff266d19f7ba92c40566e1d098c435da41e95274" + "hash": "02972d40481cee9757d1a6d6eb65b2aa3e98338e857477d879ae24ebe16c0e0c" } diff --git a/core/lib/dal/.sqlx/query-47d94abb258de733fa65eaae0f5ba6224045c4820ec9c9e4ec627a8401341b9f.json b/core/lib/dal/.sqlx/query-47d94abb258de733fa65eaae0f5ba6224045c4820ec9c9e4ec627a8401341b9f.json new file mode 100644 index 000000000000..47d13c4e2223 --- /dev/null +++ b/core/lib/dal/.sqlx/query-47d94abb258de733fa65eaae0f5ba6224045c4820ec9c9e4ec627a8401341b9f.json @@ -0,0 +1,36 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n tx_index_in_block AS \"transaction_index!\",\n topic2 AS \"deployer!\",\n topic4 AS \"deployed_address!\"\n FROM events\n WHERE miniblock_number = $1 AND address = $2 AND topic1 = $3\n ORDER BY event_index_in_block\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "transaction_index!", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "deployer!", + "type_info": "Bytea" + }, + { + "ordinal": 2, + "name": "deployed_address!", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Int8", + "Bytea", + "Bytea" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "47d94abb258de733fa65eaae0f5ba6224045c4820ec9c9e4ec627a8401341b9f" +} diff --git a/core/lib/dal/src/events_web3_dal.rs b/core/lib/dal/src/events_web3_dal.rs index 8b7eb96b7146..24beeb7cab9d 100644 --- a/core/lib/dal/src/events_web3_dal.rs +++ b/core/lib/dal/src/events_web3_dal.rs @@ -4,13 +4,22 @@ use sqlx::{ Postgres, Row, }; use zksync_db_connection::{connection::Connection, error::DalResult, instrument::InstrumentExt}; +use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; use zksync_types::{ api::{GetLogsFilter, Log}, - Address, L2BlockNumber, H256, + h256_to_address, Address, L2BlockNumber, H256, }; +use zksync_vm_interface::VmEvent; use crate::{models::storage_event::StorageWeb3Log, Core}; +#[derive(Debug, PartialEq)] +pub struct ContractDeploymentLog { + pub transaction_index_in_block: u64, + pub deployer: Address, + pub deployed_address: Address, +} + #[derive(Debug)] pub struct EventsWeb3Dal<'a, 'c> { pub(crate) storage: &'a mut Connection<'c, Core>, @@ -242,6 +251,40 @@ impl EventsWeb3Dal<'_, '_> { let logs = db_logs.into_iter().map(Into::into).collect(); Ok(logs) } + + /// Gets all contract deployment logs for the specified block. The returned logs are ordered by their execution order. + pub async fn get_contract_deployment_logs( + &mut self, + block: L2BlockNumber, + ) -> DalResult> { + let rows = sqlx::query!( + r#" + SELECT + tx_index_in_block AS "transaction_index!", + topic2 AS "deployer!", + topic4 AS "deployed_address!" + FROM events + WHERE miniblock_number = $1 AND address = $2 AND topic1 = $3 + ORDER BY event_index_in_block + "#, + i64::from(block.0), + CONTRACT_DEPLOYER_ADDRESS.as_bytes(), + VmEvent::DEPLOY_EVENT_SIGNATURE.as_bytes() + ) + .instrument("get_contract_deployment_logs") + .with_arg("block", &block) + .fetch_all(self.storage) + .await?; + + Ok(rows + .into_iter() + .map(|row| ContractDeploymentLog { + transaction_index_in_block: row.transaction_index as u64, + deployer: h256_to_address(&H256::from_slice(&row.deployer)), + deployed_address: h256_to_address(&H256::from_slice(&row.deployed_address)), + }) + .collect()) + } } #[cfg(test)] diff --git a/core/lib/dal/src/models/storage_transaction.rs b/core/lib/dal/src/models/storage_transaction.rs index 27442e41d7be..ea646adc8173 100644 --- a/core/lib/dal/src/models/storage_transaction.rs +++ b/core/lib/dal/src/models/storage_transaction.rs @@ -1,12 +1,10 @@ use std::{convert::TryInto, str::FromStr}; use bigdecimal::Zero; -use serde_json::Value; use sqlx::types::chrono::{DateTime, NaiveDateTime, Utc}; use zksync_types::{ api::{self, TransactionDetails, TransactionReceipt, TransactionStatus}, fee::Fee, - h256_to_address, l1::{OpProcessingType, PriorityQueueType}, l2::TransactionType, protocol_upgrade::ProtocolUpgradeTxCommonData, @@ -20,7 +18,9 @@ use zksync_types::{ use zksync_vm_interface::Call; use super::call::{LegacyCall, LegacyMixedCall}; -use crate::{models::bigdecimal_to_u256, BigDecimal}; +use crate::{ + models::bigdecimal_to_u256, transactions_web3_dal::ExtendedTransactionReceipt, BigDecimal, +}; #[derive(Debug, Clone, sqlx::FromRow)] #[cfg_attr(test, derive(Default))] @@ -348,15 +348,16 @@ pub(crate) struct StorageTransactionReceipt { pub l1_batch_number: Option, pub transfer_to: Option, pub execute_contract_address: Option, + pub calldata: serde_json::Value, pub refunded_gas: i64, pub gas_limit: Option, pub effective_gas_price: Option, - pub contract_address: Option>, pub initiator_address: Vec, + pub nonce: Option, pub block_timestamp: Option, } -impl From for TransactionReceipt { +impl From for ExtendedTransactionReceipt { fn from(storage_receipt: StorageTransactionReceipt) -> Self { let status = storage_receipt.error.map_or_else(U64::one, |_| U64::zero()); @@ -367,18 +368,16 @@ impl From for TransactionReceipt { .index_in_block .map_or_else(Default::default, U64::from); - // For better compatibility with various clients, we never return `None` recipient address. let to = storage_receipt .transfer_to .or(storage_receipt.execute_contract_address) .and_then(|addr| { serde_json::from_value::>(addr) .expect("invalid address value in the database") - }) - .unwrap_or_else(Address::zero); + }); let block_hash = H256::from_slice(&storage_receipt.block_hash); - TransactionReceipt { + let inner = TransactionReceipt { transaction_hash: H256::from_slice(&storage_receipt.tx_hash), transaction_index, block_hash, @@ -386,7 +385,7 @@ impl From for TransactionReceipt { l1_batch_tx_index: storage_receipt.l1_batch_tx_index.map(U64::from), l1_batch_number: storage_receipt.l1_batch_number.map(U64::from), from: H160::from_slice(&storage_receipt.initiator_address), - to: Some(to), + to, cumulative_gas_used: Default::default(), // TODO: Should be actually calculated (SMA-1183). gas_used: { let refunded_gas: U256 = storage_receipt.refunded_gas.into(); @@ -401,9 +400,7 @@ impl From for TransactionReceipt { .map(bigdecimal_to_u256) .unwrap_or_default(), ), - contract_address: storage_receipt - .contract_address - .map(|addr| h256_to_address(&H256::from_slice(&addr))), + contract_address: None, // Must be filled in separately logs: vec![], l2_to_l1_logs: vec![], status, @@ -411,6 +408,13 @@ impl From for TransactionReceipt { // Even though the Rust SDK recommends us to supply "None" for legacy transactions // we always supply some number anyway to have the same behavior as most popular RPCs transaction_type: Some(tx_type), + }; + + Self { + inner, + nonce: (storage_receipt.nonce.unwrap_or(0) as u64).into(), + calldata: serde_json::from_value(storage_receipt.calldata) + .expect("incorrect calldata in Postgres"), } } } @@ -419,7 +423,7 @@ impl From for TransactionReceipt { #[derive(Debug, Clone, sqlx::FromRow)] pub struct StorageTransactionExecutionInfo { /// This is an opaque JSON field, with VM version specific contents. - pub execution_info: Value, + pub execution_info: serde_json::Value, } #[derive(Debug, Clone, sqlx::FromRow)] diff --git a/core/lib/dal/src/transactions_web3_dal.rs b/core/lib/dal/src/transactions_web3_dal.rs index 44d7ed89c477..c6a7c787800a 100644 --- a/core/lib/dal/src/transactions_web3_dal.rs +++ b/core/lib/dal/src/transactions_web3_dal.rs @@ -9,10 +9,9 @@ use zksync_db_connection::{ interpolate_query, match_query_as, }; use zksync_types::{ - api, api::TransactionReceipt, block::build_bloom, Address, BloomInput, L2BlockNumber, - L2ChainId, Transaction, CONTRACT_DEPLOYER_ADDRESS, H256, U256, + api, api::TransactionReceipt, block::build_bloom, web3, Address, BloomInput, L2BlockNumber, + L2ChainId, Transaction, H256, U256, }; -use zksync_vm_interface::VmEvent; use crate::{ models::storage_transaction::{ @@ -28,41 +27,34 @@ enum TransactionSelector<'a> { Position(L2BlockNumber, u32), } +/// Transaction receipt together with additional data used by the API server logic. +#[derive(Debug)] +pub struct ExtendedTransactionReceipt { + pub inner: TransactionReceipt, + pub nonce: U256, + pub calldata: web3::Bytes, +} + #[derive(Debug)] pub struct TransactionsWeb3Dal<'a, 'c> { pub(crate) storage: &'a mut Connection<'c, Core>, } impl TransactionsWeb3Dal<'_, '_> { - /// Returns receipts by transactions hashes. - /// Hashes are expected to be unique. + /// Returns receipts by transactions hashes. Hashes are expected to be unique. + /// + /// # Important! + /// + /// The returned receipts do not have `contract_address` set; this field needs to be filled separately. pub async fn get_transaction_receipts( &mut self, hashes: &[H256], - ) -> DalResult> { + ) -> DalResult> { let hash_bytes: Vec<_> = hashes.iter().map(H256::as_bytes).collect(); - // Clarification for first part of the query(`WITH` clause): - // Looking for `ContractDeployed` event in the events table - // to find the address of deployed contract let st_receipts: Vec = sqlx::query_as!( StorageTransactionReceipt, r#" - WITH - events AS ( - SELECT DISTINCT - ON (events.tx_hash) * - FROM - events - WHERE - events.address = $1 - AND events.topic1 = $2 - AND events.tx_hash = ANY($3) - ORDER BY - events.tx_hash, - events.event_index_in_tx DESC - ) - SELECT transactions.hash AS tx_hash, transactions.index_in_block, @@ -73,25 +65,23 @@ impl TransactionsWeb3Dal<'_, '_> { transactions.initiator_address, transactions.data -> 'to' AS "transfer_to?", transactions.data -> 'contractAddress' AS "execute_contract_address?", + transactions.data -> 'calldata' AS "calldata", transactions.tx_format AS "tx_format?", transactions.refunded_gas, transactions.gas_limit, + transactions.nonce, miniblocks.hash AS "block_hash", miniblocks.l1_batch_number AS "l1_batch_number?", - events.topic4 AS "contract_address?", miniblocks.timestamp AS "block_timestamp?" FROM transactions JOIN miniblocks ON miniblocks.number = transactions.miniblock_number - LEFT JOIN events ON events.tx_hash = transactions.hash WHERE - transactions.hash = ANY($3) + transactions.hash = ANY($1) AND transactions.data != '{}'::jsonb "#, // ^ Filter out transactions with pruned data, which would lead to potentially incomplete / bogus // transaction info. - CONTRACT_DEPLOYER_ADDRESS.as_bytes(), - VmEvent::DEPLOY_EVENT_SIGNATURE.as_bytes(), &hash_bytes as &[&[u8]], ) .instrument("get_transaction_receipts") @@ -102,7 +92,7 @@ impl TransactionsWeb3Dal<'_, '_> { let block_timestamps: Vec> = st_receipts.iter().map(|x| x.block_timestamp).collect(); - let mut receipts: Vec = + let mut receipts: Vec = st_receipts.into_iter().map(Into::into).collect(); let mut logs = self @@ -118,6 +108,7 @@ impl TransactionsWeb3Dal<'_, '_> { .await?; for (receipt, block_timestamp) in receipts.iter_mut().zip(block_timestamps.into_iter()) { + let receipt = &mut receipt.inner; let logs_for_tx = logs.remove(&receipt.transaction_hash); if let Some(logs) = logs_for_tx { @@ -666,11 +657,11 @@ mod tests { .await .unwrap(); - receipts.sort_unstable_by_key(|receipt| receipt.transaction_index); + receipts.sort_unstable_by_key(|receipt| receipt.inner.transaction_index); assert_eq!(receipts.len(), 2); - assert_eq!(receipts[0].transaction_hash, tx1_hash); - assert_eq!(receipts[1].transaction_hash, tx2_hash); + assert_eq!(receipts[0].inner.transaction_hash, tx1_hash); + assert_eq!(receipts[1].inner.transaction_hash, tx2_hash); } #[tokio::test] @@ -693,9 +684,9 @@ mod tests { .await .unwrap(); assert_eq!(receipts.len(), 1); - let receipt = receipts.into_iter().next().unwrap(); + let receipt = receipts.into_iter().next().unwrap().inner; assert_eq!(receipt.transaction_hash, tx_hash); - assert_eq!(receipt.to, Some(Address::zero())); + assert_eq!(receipt.to, None); } #[tokio::test] diff --git a/core/lib/multivm/src/utils/deduplicator.rs b/core/lib/multivm/src/utils/deduplicator.rs index 0cb4c3fa7cd8..491068b998c5 100644 --- a/core/lib/multivm/src/utils/deduplicator.rs +++ b/core/lib/multivm/src/utils/deduplicator.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use zksync_types::{ - h256_to_u256, writes::compression::compress_with_best_strategy, StorageKey, StorageLogKind, - StorageLogWithPreviousValue, H256, + h256_to_u256, writes::compression::compress_with_best_strategy, StorageKey, StorageLog, + StorageLogKind, StorageLogWithPreviousValue, H256, }; use crate::interface::DeduplicatedWritesMetrics; @@ -51,6 +51,20 @@ impl StorageWritesDeduplicator { self.modified_key_values } + /// Deduplicates the provided logs in isolation. + pub fn deduplicate_logs<'a>( + logs: impl Iterator, + ) -> Vec { + let mut deduplicator = Self::new(); + deduplicator.apply(logs); + let deduplicated_logs = deduplicator.into_modified_key_values(); + + deduplicated_logs + .into_iter() + .map(|(key, ModifiedSlot { value, .. })| StorageLog::new_write_log(key, value)) + .collect() + } + /// Applies storage logs to the state. pub fn apply<'a, I: IntoIterator>(&mut self, logs: I) { self.process_storage_logs(logs); diff --git a/core/lib/multivm/src/versions/shadow/tests.rs b/core/lib/multivm/src/versions/shadow/tests.rs index dc7417ad1259..2fc7bba796a0 100644 --- a/core/lib/multivm/src/versions/shadow/tests.rs +++ b/core/lib/multivm/src/versions/shadow/tests.rs @@ -284,6 +284,11 @@ mod default_aa { fn default_aa_interaction() { test_default_aa_interaction::(); } + + #[test] + fn permissive_aa_works() { + test_permissive_aa_works::(); + } } mod evm_emulator { @@ -529,6 +534,11 @@ mod simple_execution { fn simple_execute() { test_simple_execute::(); } + + #[test] + fn create2_deployment_address() { + test_create2_deployment_address::(); + } } mod storage { diff --git a/core/lib/multivm/src/versions/testonly/default_aa.rs b/core/lib/multivm/src/versions/testonly/default_aa.rs index 0ae4cc2bbe50..f6a84aa56506 100644 --- a/core/lib/multivm/src/versions/testonly/default_aa.rs +++ b/core/lib/multivm/src/versions/testonly/default_aa.rs @@ -2,11 +2,14 @@ use zksync_test_contracts::{DeployContractsTx, TestContract, TxType}; use zksync_types::{ get_code_key, get_known_code_key, get_nonce_key, h256_to_u256, system_contracts::{DEPLOYMENT_NONCE_INCREMENT, TX_NONCE_INCREMENT}, - utils::storage_key_for_eth_balance, - U256, + utils::{deployed_address_create, storage_key_for_eth_balance}, + Address, Execute, U256, }; -use super::{default_pubdata_builder, tester::VmTesterBuilder, TestedVm}; +use super::{ + default_pubdata_builder, extract_deploy_events, tester::VmTesterBuilder, ContractToDeploy, + TestedVm, +}; use crate::{ interface::{InspectExecutionMode, TxExecutionMode, VmInterfaceExt}, vm_latest::utils::fee::get_batch_base_fee, @@ -65,3 +68,72 @@ pub(crate) fn test_default_aa_interaction() { ]; vm.vm.verify_required_storage(&expected_slots); } + +pub(crate) fn test_permissive_aa_works() { + let builder = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(2); + let aa_address = builder.rich_account(0).address(); + let mut vm = builder + .with_custom_contracts(vec![ContractToDeploy::account( + TestContract::permissive_account().bytecode.to_vec(), + aa_address, + )]) + .build::(); + let other_account = &mut vm.rich_accounts[1]; + + // Check that the account can be called as a contract. + let execute = Execute { + contract_address: Some(aa_address), + calldata: TestContract::permissive_account() + .function("deploy") + .encode_input(&[ethabi::Token::Uint(5.into())]) + .unwrap(), + value: 0.into(), + factory_deps: vec![TestContract::permissive_account().dependencies[0] + .bytecode + .to_vec()], + }; + let tx = other_account.get_l2_tx_for_execute(execute, None); + vm.vm.push_transaction(tx); + let res = vm.vm.execute(InspectExecutionMode::OneTx); + assert!(!res.result.is_failed(), "{res:#?}"); + + let deploy_events = extract_deploy_events(&res.logs.events); + let expected_deploy_events: Vec<_> = (0_u32..5) + .map(|nonce| { + ( + aa_address, + deployed_address_create(aa_address, nonce.into()), + ) + }) + .collect(); + assert_eq!(deploy_events, expected_deploy_events); + + let account = &mut vm.rich_accounts[0]; + let execute = Execute { + contract_address: Some(Address::repeat_byte(1)), + calldata: vec![], + value: 123.into(), + factory_deps: vec![], + }; + let transfer_tx = account.get_l2_tx_for_execute(execute, None); + vm.vm.push_transaction(transfer_tx); + let res = vm.vm.execute(InspectExecutionMode::OneTx); + assert!(!res.result.is_failed(), "{res:#?}"); + + // Check that the account works as a deployer as well. + let deploy_tx = account + .get_deploy_tx(TestContract::counter().bytecode, None, TxType::L2) + .tx; + vm.vm.push_transaction(deploy_tx); + let res = vm.vm.execute(InspectExecutionMode::OneTx); + assert!(!res.result.is_failed(), "{res:#?}"); + + let deploy_events = extract_deploy_events(&res.logs.events); + assert_eq!( + deploy_events, + [(aa_address, deployed_address_create(aa_address, 5.into()))] + ); +} diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index c1a603bfeefc..464d76040797 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -15,10 +15,12 @@ use once_cell::sync::Lazy; use zksync_contracts::{ read_bootloader_code, read_zbin_bytecode, BaseSystemContracts, SystemContractCode, }; +use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; use zksync_types::{ block::L2BlockHasher, bytecode::BytecodeHash, fee_model::BatchFeeInput, get_code_key, - get_is_account_key, h256_to_u256, u256_to_h256, utils::storage_key_for_eth_balance, Address, - L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, U256, + get_is_account_key, h256_to_address, h256_to_u256, u256_to_h256, + utils::storage_key_for_eth_balance, Address, L1BatchNumber, L2BlockNumber, L2ChainId, + ProtocolVersionId, U256, }; pub(super) use self::tester::{ @@ -28,7 +30,7 @@ pub(super) use self::tester::{ use crate::{ interface::{ pubdata::PubdataBuilder, storage::InMemoryStorage, L1BatchEnv, L2BlockEnv, SystemEnv, - TxExecutionMode, + TxExecutionMode, VmEvent, }, pubdata_builders::FullPubdataBuilder, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, @@ -189,3 +191,20 @@ impl ContractToDeploy { } } } + +fn extract_deploy_events(events: &[VmEvent]) -> Vec<(Address, Address)> { + events + .iter() + .filter_map(|event| { + if event.address == CONTRACT_DEPLOYER_ADDRESS + && event.indexed_topics[0] == VmEvent::DEPLOY_EVENT_SIGNATURE + { + let deployer = h256_to_address(&event.indexed_topics[1]); + let deployed_address = h256_to_address(&event.indexed_topics[3]); + Some((deployer, deployed_address)) + } else { + None + } + }) + .collect() +} diff --git a/core/lib/multivm/src/versions/testonly/simple_execution.rs b/core/lib/multivm/src/versions/testonly/simple_execution.rs index 13dd7d617d82..88abc7f16ec9 100644 --- a/core/lib/multivm/src/versions/testonly/simple_execution.rs +++ b/core/lib/multivm/src/versions/testonly/simple_execution.rs @@ -1,7 +1,8 @@ use assert_matches::assert_matches; -use zksync_test_contracts::TxType; +use zksync_test_contracts::{TestContract, TxType}; +use zksync_types::{Execute, H256}; -use super::{default_pubdata_builder, tester::VmTesterBuilder, TestedVm}; +use super::{default_pubdata_builder, extract_deploy_events, tester::VmTesterBuilder, TestedVm}; use crate::interface::{ExecutionResult, InspectExecutionMode, VmInterfaceExt}; pub(crate) fn test_estimate_fee() { @@ -75,3 +76,42 @@ pub(crate) fn test_simple_execute() { .block_tip_execution_result; assert_matches!(block_tip.result, ExecutionResult::Success { .. }); } + +// TODO: also test EVM contract addresses once EVM emulator is implemented +pub(crate) fn test_create2_deployment_address() { + let mut vm_tester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .build::(); + let account = &mut vm_tester.rich_accounts[0]; + + let (execute, deploy_params) = + Execute::for_create2_deploy(H256::zero(), TestContract::counter().bytecode.to_vec(), &[]); + let deploy_tx = account.get_l2_tx_for_execute(execute, None); + let expected_address = deploy_params.derive_address(account.address); + + vm_tester.vm.push_transaction(deploy_tx); + let res = vm_tester.vm.execute(InspectExecutionMode::OneTx); + assert_matches!(res.result, ExecutionResult::Success { .. }); + + let deploy_events = extract_deploy_events(&res.logs.events); + assert_eq!(deploy_events.len(), 1); + assert_eq!(deploy_events[0], (account.address, expected_address)); + + // Test with non-trivial salt and constructor args + let (execute, deploy_params) = Execute::for_create2_deploy( + H256::repeat_byte(1), + TestContract::load_test().bytecode.to_vec(), + &[ethabi::Token::Uint(100.into())], + ); + let deploy_tx = account.get_l2_tx_for_execute(execute, None); + let expected_address = deploy_params.derive_address(account.address); + + vm_tester.vm.push_transaction(deploy_tx); + let res = vm_tester.vm.execute(InspectExecutionMode::OneTx); + assert_matches!(res.result, ExecutionResult::Success { .. }); + + let deploy_events = extract_deploy_events(&res.logs.events); + assert_eq!(deploy_events.len(), 1); + assert_eq!(deploy_events[0], (account.address, expected_address)); +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs b/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs index c3cfd8b29f37..d64e579bcf13 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs @@ -1,6 +1,14 @@ -use crate::{versions::testonly::default_aa::test_default_aa_interaction, vm_fast::Vm}; +use crate::{ + versions::testonly::default_aa::{test_default_aa_interaction, test_permissive_aa_works}, + vm_fast::Vm, +}; #[test] fn default_aa_interaction() { test_default_aa_interaction::>(); } + +#[test] +fn permissive_aa_works() { + test_permissive_aa_works::>(); +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs b/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs index 4fe33d237e9e..c299a500dc68 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs @@ -1,5 +1,7 @@ use crate::{ - versions::testonly::simple_execution::{test_estimate_fee, test_simple_execute}, + versions::testonly::simple_execution::{ + test_create2_deployment_address, test_estimate_fee, test_simple_execute, + }, vm_fast::Vm, }; @@ -12,3 +14,8 @@ fn estimate_fee() { fn simple_execute() { test_simple_execute::>(); } + +#[test] +fn create2_deployment_address() { + test_create2_deployment_address::>(); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs b/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs index 3d0e21c2466f..440d5a542eb6 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs @@ -1,5 +1,5 @@ use crate::{ - versions::testonly::default_aa::test_default_aa_interaction, + versions::testonly::default_aa::{test_default_aa_interaction, test_permissive_aa_works}, vm_latest::{HistoryEnabled, Vm}, }; @@ -7,3 +7,8 @@ use crate::{ fn default_aa_interaction() { test_default_aa_interaction::>(); } + +#[test] +fn permissive_aa_works() { + test_permissive_aa_works::>(); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs b/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs index 29072e66b1ea..564066becd6d 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs @@ -1,5 +1,7 @@ use crate::{ - versions::testonly::simple_execution::{test_estimate_fee, test_simple_execute}, + versions::testonly::simple_execution::{ + test_create2_deployment_address, test_estimate_fee, test_simple_execute, + }, vm_latest::{HistoryEnabled, Vm}, }; @@ -12,3 +14,8 @@ fn estimate_fee() { fn simple_execute() { test_simple_execute::>(); } + +#[test] +fn create2_deployment_address() { + test_create2_deployment_address::>(); +} diff --git a/core/lib/test_contracts/contracts/custom-account/permissive.sol b/core/lib/test_contracts/contracts/custom-account/permissive.sol new file mode 100644 index 000000000000..5fbaca3cb7e9 --- /dev/null +++ b/core/lib/test_contracts/contracts/custom-account/permissive.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +import './interfaces/IAccount.sol'; +import './interfaces/INonceHolder.sol'; +import './SystemContractsCaller.sol'; +import './TransactionHelper.sol'; + +/** Account abstraction that can be called as a contract. */ +contract PermissiveAccount is IAccount { + using TransactionHelper for Transaction; + + function deploy(uint _count) external { + for (uint i = 0; i < _count; i++) { + new PermissiveAccountDeployedContract(); + } + } + + function validateTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) external payable override returns (bytes4 magic) { + SystemContractsCaller.systemCallWithPropagatedRevert( + uint32(gasleft()), + address(0x8003), // NonceHolder address + 0, + abi.encodeCall( + INonceHolder.incrementMinNonceIfEquals, + (_transaction.nonce) + ) + ); + + magic = VALIDATION_SUCCESS_MAGIC; // always succeed + } + + function executeTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) external payable override { + _execute(_transaction); + } + + function executeTransactionFromOutside(Transaction calldata _transaction) external payable override { + _execute(_transaction); + } + + function _execute(Transaction calldata _transaction) internal { + address to = address(uint160(_transaction.to)); + uint256 value = _transaction.value; + bytes memory data = _transaction.data; + + if (to == address(0x8006)) { // ContractDeployer + // `ContractDeployer` deployments require a system call + SystemContractsCaller.systemCallWithPropagatedRevert( + uint32(gasleft()), + to, + uint128(value), + _transaction.data + ); + } else { + (bool success, bytes memory returnData) = to.call{value: value}(data); + if (!success) { + assembly { + let size := mload(returnData) + revert(add(returnData, 0x20), size) + } + } + } + } + + function payForTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) external payable override { + bool success = _transaction.payToTheBootloader(); + require(success, "Failed to pay the fee to the operator"); + } + + function prepareForPaymaster( + bytes32, + bytes32, + Transaction calldata _transaction + ) external payable override { + _transaction.processPaymasterInput(); + } + + // Behave like EOA by default + fallback() external payable {} + receive() external payable {} +} + +contract PermissiveAccountDeployedContract {} diff --git a/core/lib/test_contracts/src/contracts.rs b/core/lib/test_contracts/src/contracts.rs index a997f70f6870..bea4a0426ab8 100644 --- a/core/lib/test_contracts/src/contracts.rs +++ b/core/lib/test_contracts/src/contracts.rs @@ -95,6 +95,17 @@ impl TestContract { &CONTRACT } + pub fn permissive_account() -> &'static Self { + static CONTRACT: Lazy = Lazy::new(|| { + let mut contract = TestContract::new(raw::custom_account::PermissiveAccount); + contract.dependencies = vec![TestContract::new( + raw::custom_account::PermissiveAccountDeployedContract, + )]; + contract + }); + &CONTRACT + } + /// Returns a custom account with multiple owners. pub fn many_owners() -> &'static Self { static CONTRACT: Lazy = diff --git a/core/lib/types/src/tx/execute.rs b/core/lib/types/src/tx/execute.rs index d36f4b6521ee..94b02c48356f 100644 --- a/core/lib/types/src/tx/execute.rs +++ b/core/lib/types/src/tx/execute.rs @@ -1,6 +1,6 @@ use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use zksync_basic_types::bytecode::BytecodeHash; +use zksync_basic_types::{address_to_h256, bytecode::BytecodeHash, web3::keccak256}; use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; use crate::{ @@ -88,6 +88,29 @@ impl EIP712TypedStructure for Execute { } } +const CREATE_PARAMS: &[ethabi::ParamType] = &[ + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::Bytes, +]; +const CREATE_ACC_PARAMS: &[ethabi::ParamType] = &[ + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::Bytes, + ethabi::ParamType::Uint(8), +]; + +// TODO (SMA-1608): We should not re-implement the ABI parts in different places, instead have the ABI available +// from the `zksync_contracts` crate. +static CREATE_FUNCTION: Lazy<[u8; 4]> = + Lazy::new(|| ethabi::short_signature("create", CREATE_PARAMS)); +static CREATE2_FUNCTION: Lazy<[u8; 4]> = + Lazy::new(|| ethabi::short_signature("create2", CREATE_PARAMS)); +static CREATE_ACC_FUNCTION: Lazy<[u8; 4]> = + Lazy::new(|| ethabi::short_signature("createAccount", CREATE_ACC_PARAMS)); +static CREATE2_ACC_FUNCTION: Lazy<[u8; 4]> = + Lazy::new(|| ethabi::short_signature("create2Account", CREATE_ACC_PARAMS)); + impl Execute { pub fn calldata(&self) -> &[u8] { &self.calldata @@ -99,25 +122,12 @@ impl Execute { contract_hash: H256, constructor_input: Vec, ) -> Vec { - // TODO (SMA-1608): We should not re-implement the ABI parts in different places, instead have the ABI available - // from the `zksync_contracts` crate. - static FUNCTION_SIGNATURE: Lazy<[u8; 4]> = Lazy::new(|| { - ethabi::short_signature( - "create", - &[ - ethabi::ParamType::FixedBytes(32), - ethabi::ParamType::FixedBytes(32), - ethabi::ParamType::Bytes, - ], - ) - }); let params = ethabi::encode(&[ ethabi::Token::FixedBytes(salt.as_bytes().to_vec()), ethabi::Token::FixedBytes(contract_hash.as_bytes().to_vec()), ethabi::Token::Bytes(constructor_input), ]); - - FUNCTION_SIGNATURE.iter().copied().chain(params).collect() + CREATE_FUNCTION.iter().copied().chain(params).collect() } /// Creates an instance for deploying the specified bytecode without additional dependencies. If necessary, @@ -140,6 +150,36 @@ impl Execute { } } + /// Creates an instance for deploying the specified bytecode via `ContractDeployer.create2` without additional dependencies. + /// If necessary, additional deps can be added to `Self.factory_deps` after this call. + pub fn for_create2_deploy( + salt: H256, + contract_bytecode: Vec, + constructor_input: &[ethabi::Token], + ) -> (Self, Create2DeploymentParams) { + let bytecode_hash = BytecodeHash::for_bytecode(&contract_bytecode).value(); + let raw_constructor_input = ethabi::encode(constructor_input); + let params = ethabi::encode(&[ + ethabi::Token::FixedBytes(salt.as_bytes().to_vec()), + ethabi::Token::FixedBytes(bytecode_hash.as_bytes().to_vec()), + ethabi::Token::Bytes(raw_constructor_input.clone()), + ]); + let calldata = CREATE2_FUNCTION.iter().copied().chain(params).collect(); + let execute = Self { + contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), + calldata, + value: 0.into(), + factory_deps: vec![contract_bytecode], + }; + + let deployment_params = Create2DeploymentParams { + salt, + bytecode_hash, + raw_constructor_input, + }; + (execute, deployment_params) + } + /// Creates an instance for transferring base token to the specified recipient. pub fn transfer(to: Address, value: U256) -> Self { Self { @@ -150,3 +190,87 @@ impl Execute { } } } + +/// Deployment params for a `ContractDeployer.{create2, create2Account}` call. +#[derive(Debug)] +pub struct Create2DeploymentParams { + pub salt: H256, + pub bytecode_hash: H256, + pub raw_constructor_input: Vec, +} + +impl Create2DeploymentParams { + /// Assumes tokens have expected shape. + fn from_tokens(tokens: Vec) -> Self { + let mut tokens = tokens.into_iter(); + // Salt is the first token. `unwrap()`s are safe because of the successful decoding. + let salt = tokens.next().unwrap(); + let salt = H256::from_slice(&salt.into_fixed_bytes().unwrap()); + // Bytecode hash is the second token. + let bytecode_hash = tokens.next().unwrap(); + let bytecode_hash = H256::from_slice(&bytecode_hash.into_fixed_bytes().unwrap()); + // Raw constructor input is the 3rd token. + let raw_constructor_input = tokens.next().unwrap(); + let raw_constructor_input = raw_constructor_input.into_bytes().unwrap(); + + Self { + salt, + bytecode_hash, + raw_constructor_input, + } + } + + /// Pre-calculates the address of the to-be-deployed EraVM contract (via CREATE2). + pub fn derive_address(&self, sender: Address) -> Address { + let prefix_bytes = keccak256("zksyncCreate2".as_bytes()); + let address_bytes = address_to_h256(&sender); + + let mut bytes = [0u8; 160]; + bytes[..32].copy_from_slice(&prefix_bytes); + bytes[32..64].copy_from_slice(address_bytes.as_bytes()); + bytes[64..96].copy_from_slice(self.salt.as_bytes()); + bytes[96..128].copy_from_slice(self.bytecode_hash.as_bytes()); + bytes[128..].copy_from_slice(&keccak256(&self.raw_constructor_input)); + + Address::from_slice(&keccak256(&bytes)[12..]) + } +} + +/// Deployment params encoding various canonical ways to deploy EraVM contracts. +#[derive(Debug)] +pub enum DeploymentParams { + Create, + Create2(Create2DeploymentParams), + CreateAccount, + Create2Account(Create2DeploymentParams), +} + +impl DeploymentParams { + pub fn decode(calldata: &[u8]) -> anyhow::Result> { + if calldata.len() < 4 { + return Ok(None); + } + let (short_signature, token_data) = calldata.split_at(4); + Ok(match short_signature { + sig if sig == *CREATE_FUNCTION => { + ethabi::decode(CREATE_PARAMS, token_data)?; + Some(Self::Create) + } + sig if sig == *CREATE2_FUNCTION => { + let tokens = ethabi::decode(CREATE_PARAMS, token_data)?; + Some(Self::Create2(Create2DeploymentParams::from_tokens(tokens))) + } + sig if sig == *CREATE_ACC_FUNCTION => { + ethabi::decode(CREATE_ACC_PARAMS, token_data)?; + Some(Self::CreateAccount) + } + sig if sig == *CREATE2_ACC_FUNCTION => { + let tokens = ethabi::decode(CREATE_ACC_PARAMS, token_data)?; + Some(Self::Create2Account(Create2DeploymentParams::from_tokens( + tokens, + ))) + } + _ => None, + }) + } +} diff --git a/core/lib/types/src/utils.rs b/core/lib/types/src/utils.rs index 56a8ccf9fe9f..b5c67db28a54 100644 --- a/core/lib/types/src/utils.rs +++ b/core/lib/types/src/utils.rs @@ -89,7 +89,7 @@ pub fn storage_key_for_eth_balance(address: &Address) -> StorageKey { storage_key_for_standard_token_balance(AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), address) } -/// Pre-calculated the address of the to-be-deployed contract (via CREATE, not CREATE2). +/// Pre-calculates the address of the to-be-deployed EraVM contract (via CREATE, not CREATE2). pub fn deployed_address_create(sender: Address, deploy_nonce: U256) -> Address { let prefix_bytes = keccak256("zksyncCreate".as_bytes()); let address_bytes = address_to_h256(&sender); @@ -103,13 +103,22 @@ pub fn deployed_address_create(sender: Address, deploy_nonce: U256) -> Address { Address::from_slice(&keccak256(&bytes)[12..]) } +/// Pre-calculates the address of the EVM contract created using a deployment transaction. +pub fn deployed_address_evm_create(sender: Address, tx_nonce: U256) -> Address { + let mut stream = rlp::RlpStream::new(); + stream + .begin_unbounded_list() + .append(&sender) + .append(&tx_nonce) + .finalize_unbounded_list(); + Address::from_slice(&keccak256(&stream.out())[12..]) +} + #[cfg(test)] mod tests { use std::str::FromStr; - use crate::{ - utils::storage_key_for_standard_token_balance, AccountTreeId, Address, StorageKey, H256, - }; + use super::*; #[test] fn test_storage_key_for_eth_token() { @@ -132,4 +141,33 @@ mod tests { assert_eq!(expected_storage_key, calculated_storage_key); } } + + // Test vectors are taken from geth: https://github.com/ethereum/go-ethereum/blob/033de2a05bdbea87b4efc5156511afe42c38fd55/crypto/crypto_test.go#L133 + #[test] + fn deployment_address_is_correctly_evaluated() { + let sender: Address = "0x970e8128ab834e8eac17ab8e3812f010678cf791" + .parse() + .unwrap(); + let address0 = deployed_address_evm_create(sender, 0.into()); + assert_eq!( + address0, + "0x333c3310824b7c685133f2bedb2ca4b8b4df633d" + .parse() + .unwrap() + ); + let address1 = deployed_address_evm_create(sender, 1.into()); + assert_eq!( + address1, + "0x8bda78331c916a08481428e4b07c96d3e916d165" + .parse() + .unwrap() + ); + let address2 = deployed_address_evm_create(sender, 2.into()); + assert_eq!( + address2, + "0xc9ddedf451bc62ce88bf9292afb13df35b670699" + .parse() + .unwrap() + ); + } } diff --git a/core/node/api_server/src/execution_sandbox/tests.rs b/core/node/api_server/src/execution_sandbox/tests.rs index 0aff15b973e0..adaec47a5e4f 100644 --- a/core/node/api_server/src/execution_sandbox/tests.rs +++ b/core/node/api_server/src/execution_sandbox/tests.rs @@ -9,11 +9,12 @@ use zksync_multivm::{interface::ExecutionResult, utils::derive_base_fee_and_gas_ use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; use zksync_node_test_utils::{create_l1_batch, create_l2_block, prepare_recovery_snapshot}; use zksync_state::PostgresStorageCaches; +use zksync_test_contracts::Account; use zksync_types::{ api::state_override::{OverrideAccount, StateOverride}, fee::Fee, fee_model::BatchFeeInput, - K256PrivateKey, ProtocolVersionId, Transaction, U256, + Address, ProtocolVersionId, Transaction, U256, }; use super::*; @@ -223,7 +224,8 @@ async fn test_instantiating_vm(connection: Connection<'static, Core>, block_args let fee_input = BatchFeeInput::l1_pegged(55, 555); let (base_fee, gas_per_pubdata) = derive_base_fee_and_gas_per_pubdata(fee_input, ProtocolVersionId::latest().into()); - let tx = K256PrivateKey::random().create_transfer_with_fee( + let tx = Account::random().create_transfer_with_fee( + Address::random(), 0.into(), Fee { gas_limit: 200_000.into(), @@ -272,7 +274,8 @@ async fn validating_transaction(set_balance: bool) { let fee_input = BatchFeeInput::l1_pegged(55, 555); let (base_fee, gas_per_pubdata) = derive_base_fee_and_gas_per_pubdata(fee_input, ProtocolVersionId::latest().into()); - let tx = K256PrivateKey::random().create_transfer_with_fee( + let tx = Account::random().create_transfer_with_fee( + Address::random(), 0.into(), Fee { gas_limit: 200_000.into(), diff --git a/core/node/api_server/src/testonly.rs b/core/node/api_server/src/testonly.rs index 06b31427ed61..86b5c1630f8f 100644 --- a/core/node/api_server/src/testonly.rs +++ b/core/node/api_server/src/testonly.rs @@ -2,15 +2,33 @@ use std::{collections::HashMap, iter}; +use assert_matches::assert_matches; use zk_evm_1_5_0::zkevm_opcode_defs::decoding::{EncodingModeProduction, VmEncodingMode}; use zksync_contracts::{eth_contract, load_contract, read_bytecode}; -use zksync_dal::{Connection, Core, CoreDal}; -use zksync_multivm::utils::derive_base_fee_and_gas_per_pubdata; -use zksync_system_constants::{L2_BASE_TOKEN_ADDRESS, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE}; -use zksync_test_contracts::{LoadnextContractExecutionParams, TestContract}; +use zksync_dal::{ + transactions_dal::L2TxSubmissionResult, Connection, ConnectionPool, Core, CoreDal, +}; +use zksync_multivm::{ + interface::{ + tracer::ValidationTraces, ExecutionResult, TransactionExecutionMetrics, + TransactionExecutionResult, TxExecutionStatus, VmExecutionMetrics, + }, + utils::{derive_base_fee_and_gas_per_pubdata, StorageWritesDeduplicator}, +}; +use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; +use zksync_node_test_utils::{create_l2_block, default_l1_batch_env, default_system_env}; +use zksync_state::PostgresStorage; +use zksync_system_constants::{ + CONTRACT_DEPLOYER_ADDRESS, L2_BASE_TOKEN_ADDRESS, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, + SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, +}; +use zksync_test_contracts::{Account, LoadnextContractExecutionParams, TestContract}; use zksync_types::{ address_to_u256, api::state_override::{Bytecode, OverrideAccount, OverrideState, StateOverride}, + block::{pack_block_info, L2BlockHeader}, + bytecode::BytecodeHash, + commitment::PubdataParams, ethabi, ethabi::Token, fee::Fee, @@ -18,12 +36,14 @@ use zksync_types::{ get_code_key, get_known_code_key, l1::L1Tx, l2::L2Tx, - transaction_request::{CallRequest, Eip712Meta, PaymasterParams}, + transaction_request::{CallRequest, Eip712Meta}, + tx::{execute::Create2DeploymentParams, IncludedTxLocation}, u256_to_h256, utils::storage_key_for_eth_balance, - AccountTreeId, Address, K256PrivateKey, L2BlockNumber, L2ChainId, Nonce, ProtocolVersionId, - StorageKey, StorageLog, EIP_712_TX_TYPE, H256, U256, + AccountTreeId, Address, Execute, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, + StorageLog, Transaction, EIP_712_TX_TYPE, H256, U256, }; +use zksync_vm_executor::{batch::MainBatchExecutorFactory, interface::BatchExecutorFactory}; const MULTICALL3_CONTRACT_PATH: &str = "contracts/l2-contracts/zkout/Multicall3.sol/Multicall3.json"; @@ -37,7 +57,7 @@ fn inflate_bytecode(bytecode: &mut Vec, nop_count: usize) { ); } -fn default_fee() -> Fee { +pub(crate) fn default_fee() -> Fee { let fee_input = FeeParams::sensible_v1_default().scale(1.0, 1.0); let (max_fee_per_gas, gas_per_pubdata_limit) = derive_base_fee_and_gas_per_pubdata(fee_input, ProtocolVersionId::default().into()); @@ -288,51 +308,50 @@ pub(crate) fn decode_u256_output(raw_output: &[u8]) -> U256 { } pub(crate) trait TestAccount { - fn create_transfer(&self, value: U256) -> L2Tx { + fn create_transfer(&mut self, value: U256) -> L2Tx { let fee = Fee { gas_limit: 200_000.into(), ..default_fee() }; - self.create_transfer_with_fee(value, fee) + self.create_transfer_with_fee(Address::random(), value, fee) } fn query_base_token_balance(&self) -> CallRequest; - fn create_transfer_with_fee(&self, value: U256, fee: Fee) -> L2Tx; + fn create_transfer_with_fee(&mut self, to: Address, value: U256, fee: Fee) -> L2Tx; - fn create_load_test_tx(&self, params: LoadnextContractExecutionParams) -> L2Tx; + fn create_load_test_tx(&mut self, params: LoadnextContractExecutionParams) -> L2Tx; - fn create_expensive_tx(&self, write_count: usize) -> L2Tx; + fn create_expensive_tx(&mut self, write_count: usize) -> L2Tx; - fn create_expensive_cleanup_tx(&self) -> L2Tx; + fn create_expensive_cleanup_tx(&mut self) -> L2Tx; - fn create_code_oracle_tx(&self, bytecode_hash: H256, expected_keccak_hash: H256) -> L2Tx; + fn create_code_oracle_tx(&mut self, bytecode_hash: H256, expected_keccak_hash: H256) -> L2Tx; - fn create_counter_tx(&self, increment: U256, revert: bool) -> L2Tx; + fn create_counter_tx(&mut self, increment: U256, revert: bool) -> L2Tx; fn create_l1_counter_tx(&self, increment: U256, revert: bool) -> L1Tx; fn query_counter_value(&self) -> CallRequest; - fn create_infinite_loop_tx(&self) -> L2Tx; + fn create_infinite_loop_tx(&mut self) -> L2Tx; fn multicall_with_value(&self, value: U256, calls: &[Call3Value]) -> CallRequest; + + fn create2_account(&mut self, bytecode: Vec) -> (L2Tx, Address); } -impl TestAccount for K256PrivateKey { - fn create_transfer_with_fee(&self, value: U256, fee: Fee) -> L2Tx { - L2Tx::new_signed( - Some(Address::random()), - vec![], - Nonce(0), - fee, +impl TestAccount for Account { + fn create_transfer_with_fee(&mut self, to: Address, value: U256, fee: Fee) -> L2Tx { + let execute = Execute { + contract_address: Some(to), + calldata: vec![], value, - L2ChainId::default(), - self, - vec![], - PaymasterParams::default(), - ) - .unwrap() + factory_deps: vec![], + }; + self.get_l2_tx_for_execute(execute, Some(fee)) + .try_into() + .unwrap() } fn query_base_token_balance(&self) -> CallRequest { @@ -349,64 +368,55 @@ impl TestAccount for K256PrivateKey { } } - fn create_load_test_tx(&self, params: LoadnextContractExecutionParams) -> L2Tx { - L2Tx::new_signed( - Some(StateBuilder::LOAD_TEST_ADDRESS), - params.to_bytes(), - Nonce(0), - default_fee(), - 0.into(), - L2ChainId::default(), - self, - if params.deploys > 0 { + fn create_load_test_tx(&mut self, params: LoadnextContractExecutionParams) -> L2Tx { + let execute = Execute { + contract_address: Some(StateBuilder::LOAD_TEST_ADDRESS), + calldata: params.to_bytes(), + value: 0.into(), + factory_deps: if params.deploys > 0 { TestContract::load_test().factory_deps() } else { vec![] }, - PaymasterParams::default(), - ) - .unwrap() + }; + self.get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap() } - fn create_expensive_tx(&self, write_count: usize) -> L2Tx { + fn create_expensive_tx(&mut self, write_count: usize) -> L2Tx { let calldata = TestContract::expensive() .function("expensive") .encode_input(&[Token::Uint(write_count.into())]) .expect("failed encoding `expensive` function"); - L2Tx::new_signed( - Some(StateBuilder::EXPENSIVE_CONTRACT_ADDRESS), + let execute = Execute { + contract_address: Some(StateBuilder::EXPENSIVE_CONTRACT_ADDRESS), calldata, - Nonce(0), - default_fee(), - 0.into(), - L2ChainId::default(), - self, - vec![], - PaymasterParams::default(), - ) - .unwrap() + value: 0.into(), + factory_deps: vec![], + }; + self.get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap() } - fn create_expensive_cleanup_tx(&self) -> L2Tx { + fn create_expensive_cleanup_tx(&mut self) -> L2Tx { let calldata = TestContract::expensive() .function("cleanUp") .encode_input(&[]) .expect("failed encoding `cleanUp` input"); - L2Tx::new_signed( - Some(StateBuilder::EXPENSIVE_CONTRACT_ADDRESS), + let execute = Execute { + contract_address: Some(StateBuilder::EXPENSIVE_CONTRACT_ADDRESS), calldata, - Nonce(0), - default_fee(), - 0.into(), - L2ChainId::default(), - self, - vec![], - PaymasterParams::default(), - ) - .unwrap() + value: 0.into(), + factory_deps: vec![], + }; + self.get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap() } - fn create_code_oracle_tx(&self, bytecode_hash: H256, expected_keccak_hash: H256) -> L2Tx { + fn create_code_oracle_tx(&mut self, bytecode_hash: H256, expected_keccak_hash: H256) -> L2Tx { let calldata = TestContract::precompiles_test() .function("callCodeOracle") .encode_input(&[ @@ -414,37 +424,31 @@ impl TestAccount for K256PrivateKey { Token::FixedBytes(expected_keccak_hash.0.to_vec()), ]) .expect("failed encoding `callCodeOracle` input"); - L2Tx::new_signed( - Some(StateBuilder::PRECOMPILES_CONTRACT_ADDRESS), + let execute = Execute { + contract_address: Some(StateBuilder::PRECOMPILES_CONTRACT_ADDRESS), calldata, - Nonce(0), - default_fee(), - 0.into(), - L2ChainId::default(), - self, - vec![], - PaymasterParams::default(), - ) - .unwrap() + value: 0.into(), + factory_deps: vec![], + }; + self.get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap() } - fn create_counter_tx(&self, increment: U256, revert: bool) -> L2Tx { + fn create_counter_tx(&mut self, increment: U256, revert: bool) -> L2Tx { let calldata = TestContract::counter() .function("incrementWithRevert") .encode_input(&[Token::Uint(increment), Token::Bool(revert)]) .expect("failed encoding `incrementWithRevert` input"); - L2Tx::new_signed( - Some(StateBuilder::COUNTER_CONTRACT_ADDRESS), + let execute = Execute { + contract_address: Some(StateBuilder::COUNTER_CONTRACT_ADDRESS), calldata, - Nonce(0), - default_fee(), - 0.into(), - L2ChainId::default(), - self, - vec![], - PaymasterParams::default(), - ) - .unwrap() + value: 0.into(), + factory_deps: vec![], + }; + self.get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap() } fn create_l1_counter_tx(&self, increment: U256, revert: bool) -> L1Tx { @@ -479,23 +483,20 @@ impl TestAccount for K256PrivateKey { } } - fn create_infinite_loop_tx(&self) -> L2Tx { + fn create_infinite_loop_tx(&mut self) -> L2Tx { let calldata = TestContract::infinite_loop() .function("infiniteLoop") .encode_input(&[]) .expect("failed encoding `infiniteLoop` input"); - L2Tx::new_signed( - Some(StateBuilder::INFINITE_LOOP_CONTRACT_ADDRESS), + let execute = Execute { + contract_address: Some(StateBuilder::INFINITE_LOOP_CONTRACT_ADDRESS), calldata, - Nonce(0), - default_fee(), - 0.into(), - L2ChainId::default(), - self, - vec![], - PaymasterParams::default(), - ) - .unwrap() + value: 0.into(), + factory_deps: vec![], + }; + self.get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap() } fn multicall_with_value(&self, value: U256, calls: &[Call3Value]) -> CallRequest { @@ -513,4 +514,266 @@ impl TestAccount for K256PrivateKey { ..CallRequest::default() } } + + fn create2_account(&mut self, bytecode: Vec) -> (L2Tx, Address) { + let create2_params = Create2DeploymentParams { + salt: H256::zero(), + bytecode_hash: BytecodeHash::for_bytecode(&bytecode).value(), + raw_constructor_input: vec![], + }; + let deployed_address = create2_params.derive_address(self.address()); + + let calldata = zksync_contracts::deployer_contract() + .function("create2Account") + .expect("no `create2Account` function") + .encode_input(&[ + Token::FixedBytes(create2_params.salt.as_bytes().to_vec()), + Token::FixedBytes(create2_params.bytecode_hash.as_bytes().to_vec()), + Token::Bytes(create2_params.raw_constructor_input), + Token::Uint(1.into()), // AA version + ]) + .expect("failed encoding `create2Account` input"); + let execute = Execute { + contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), + calldata, + value: 0.into(), + factory_deps: vec![bytecode], + }; + let deploy_tx = self + .get_l2_tx_for_execute(execute, Some(default_fee())) + .try_into() + .unwrap(); + (deploy_tx, deployed_address) + } +} + +pub(crate) fn mock_execute_transaction(transaction: Transaction) -> TransactionExecutionResult { + TransactionExecutionResult { + hash: transaction.hash(), + transaction, + execution_info: VmExecutionMetrics::default(), + execution_status: TxExecutionStatus::Success, + refunded_gas: 0, + operator_suggested_refund: 0, + compressed_bytecodes: vec![], + call_traces: vec![], + revert_reason: None, + } +} + +pub(crate) async fn store_custom_l2_block( + storage: &mut Connection<'_, Core>, + header: &L2BlockHeader, + transaction_results: &[TransactionExecutionResult], +) -> anyhow::Result<()> { + let number = header.number; + for result in transaction_results { + let l2_tx = result.transaction.clone().try_into().unwrap(); + let tx_submission_result = storage + .transactions_dal() + .insert_transaction_l2( + &l2_tx, + TransactionExecutionMetrics::default(), + ValidationTraces::default(), + ) + .await + .unwrap(); + assert_matches!(tx_submission_result, L2TxSubmissionResult::Added); + } + + // Record L2 block info which is read by the VM sandbox logic + let l2_block_info_key = StorageKey::new( + AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), + SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, + ); + let block_info = pack_block_info(number.0.into(), number.0.into()); + let l2_block_log = StorageLog::new_write_log(l2_block_info_key, u256_to_h256(block_info)); + storage + .storage_logs_dal() + .append_storage_logs(number, &[l2_block_log]) + .await?; + + storage.blocks_dal().insert_l2_block(header).await?; + storage + .transactions_dal() + .mark_txs_as_executed_in_l2_block( + number, + transaction_results, + 1.into(), + ProtocolVersionId::latest(), + false, + ) + .await?; + Ok(()) +} + +/// Executes transactions and stores real events and storage logs. +pub(crate) async fn persist_block_with_transactions( + pool: &ConnectionPool, + txs: Vec, +) { + let mut storage = pool.connection().await.unwrap(); + let prev_block = storage + .blocks_dal() + .get_last_sealed_l2_block_header() + .await + .unwrap() + .expect("no blocks in storage"); + assert_eq!(prev_block.number, L2BlockNumber(0)); + let block_header = create_l2_block(1); + let block_number = block_header.number; + + let system_env = default_system_env(); + let mut l1_batch_env = default_l1_batch_env(1, 1, Address::repeat_byte(1)); + l1_batch_env.first_l2_block.prev_block_hash = prev_block.hash; + l1_batch_env.previous_batch_hash = Some( + storage + .blocks_dal() + .get_l1_batch_state_root(L1BatchNumber(0)) + .await + .unwrap() + .expect("no root hash for genesis L1 batch"), + ); + + let executor_storage = PostgresStorage::new_async( + tokio::runtime::Handle::current(), + pool.connection().await.unwrap(), + block_number - 1, + false, + ) + .await + .unwrap(); + + let mut batch_executor = MainBatchExecutorFactory::<()>::new(false).init_batch( + executor_storage, + l1_batch_env, + system_env, + PubdataParams::default(), + ); + + let mut all_events = vec![]; + let mut events_by_transaction = vec![]; + let mut all_logs = vec![]; + let mut all_tx_results = vec![]; + for (i, tx) in txs.into_iter().enumerate() { + let tx_result = batch_executor + .execute_tx(tx.clone()) + .await + .unwrap() + .tx_result; + + let start_idx = all_events.len(); + all_events.extend(tx_result.logs.events); + let tx_location = IncludedTxLocation { + tx_hash: tx.hash(), + tx_index_in_l2_block: i as u32, + tx_initiator_address: tx.initiator_account(), + }; + events_by_transaction.push((tx_location, start_idx..all_events.len())); + all_logs.extend(tx_result.logs.storage_logs); + all_tx_results.push(TransactionExecutionResult { + execution_status: match tx_result.result { + ExecutionResult::Success { .. } => TxExecutionStatus::Success, + ExecutionResult::Revert { .. } => TxExecutionStatus::Failure, + other => panic!("unexpected tx result: {other:?}"), + }, + ..mock_execute_transaction(tx) + }); + } + + let events_by_transaction: Vec<_> = events_by_transaction + .into_iter() + .map(|(location, range)| (location, all_events[range].iter().collect::>())) + .collect(); + + storage + .events_dal() + .save_events(block_number, &events_by_transaction) + .await + .unwrap(); + let deduplicated_logs = StorageWritesDeduplicator::deduplicate_logs(all_logs.iter()); + storage + .storage_logs_dal() + .insert_storage_logs(block_number, &deduplicated_logs) + .await + .unwrap(); + store_custom_l2_block(&mut storage, &block_header, &all_tx_results) + .await + .unwrap(); +} + +#[cfg(test)] +mod tests { + use zksync_test_contracts::TxType; + + use super::*; + + #[tokio::test] + async fn persisting_block_with_transactions_works() { + let mut alice = Account::random(); + let transfer = alice.create_transfer(1.into()); + let transfer_hash = transfer.hash(); + let deployment = alice + .get_deploy_tx(TestContract::counter().bytecode, None, TxType::L2) + .tx; + let deployment_hash = deployment.hash(); + + let pool = ConnectionPool::test_pool().await; + let mut storage = pool.connection().await.unwrap(); + insert_genesis_batch(&mut storage, &GenesisParams::mock()) + .await + .unwrap(); + let balance_key = storage_key_for_eth_balance(&alice.address()); + let balance_log = StorageLog::new_write_log(balance_key, H256::from_low_u64_be(u64::MAX)); + storage + .storage_logs_dal() + .append_storage_logs(L2BlockNumber(0), &[balance_log]) + .await + .unwrap(); + + persist_block_with_transactions(&pool, vec![transfer.into(), deployment]).await; + + let mut receipts = storage + .transactions_web3_dal() + .get_transaction_receipts(&[transfer_hash, deployment_hash]) + .await + .unwrap(); + assert_eq!(receipts.len(), 2); + receipts.sort_unstable_by_key(|receipt| receipt.inner.transaction_index); + + assert_eq!(receipts[0].inner.from, alice.address()); + assert_eq!(receipts[0].inner.transaction_hash, transfer_hash); + assert_eq!(receipts[0].inner.status, 1.into()); + assert_eq!(receipts[0].nonce, 0.into()); + assert_eq!(receipts[0].calldata.0, [] as [u8; 0]); + + assert_eq!(receipts[1].inner.from, alice.address()); + assert_eq!(receipts[1].inner.transaction_hash, deployment_hash); + assert_eq!(receipts[1].inner.status, 1.into()); + assert_eq!(receipts[1].nonce, 1.into()); + assert!(!receipts[1].calldata.0.is_empty()); + + // Check that the transactions have storage logs and events persisted + let new_storage_logs: Vec<_> = storage + .storage_logs_dal() + .dump_all_storage_logs_for_tests() + .await + .into_iter() + .filter(|log| log.l2_block_number == L2BlockNumber(1)) + .collect(); + assert!(!new_storage_logs.is_empty()); + assert!( + new_storage_logs + .iter() + .any(|log| log.hashed_key == balance_key.hashed_key()), + "{new_storage_logs:#?}" + ); + + let new_events = storage + .events_web3_dal() + .get_all_logs(L2BlockNumber(0)) + .await + .unwrap(); + assert!(!new_events.is_empty()); + } } diff --git a/core/node/api_server/src/tx_sender/tests/call.rs b/core/node/api_server/src/tx_sender/tests/call.rs index 08571790e8eb..113b6f5ac72b 100644 --- a/core/node/api_server/src/tx_sender/tests/call.rs +++ b/core/node/api_server/src/tx_sender/tests/call.rs @@ -5,9 +5,8 @@ use std::collections::HashMap; use assert_matches::assert_matches; use zksync_multivm::interface::ExecutionResult; use zksync_node_test_utils::create_l2_transaction; -use zksync_types::{ - api::state_override::OverrideAccount, transaction_request::CallRequest, K256PrivateKey, -}; +use zksync_test_contracts::Account; +use zksync_types::{api::state_override::OverrideAccount, transaction_request::CallRequest}; use super::*; use crate::testonly::{decode_u256_output, Call3Result, Call3Value, StateBuilder, TestAccount}; @@ -77,7 +76,7 @@ async fn test_call( #[tokio::test] async fn eth_call_with_balance() { - let alice = K256PrivateKey::random(); + let alice = Account::random(); let initial_balance = 123_456_789.into(); let account_overrides = OverrideAccount { balance: Some(initial_balance), @@ -94,7 +93,7 @@ async fn eth_call_with_balance() { #[tokio::test] async fn eth_call_with_transfer() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let transfer_value = 1_000_000_000.into(); let initial_balance = transfer_value * 5 / 3; let state_override = StateBuilder::default() @@ -128,7 +127,7 @@ async fn eth_call_with_transfer() { #[tokio::test] async fn eth_call_with_counter() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_counter_contract(42).build(); let pool = ConnectionPool::::constrained_test_pool(1).await; @@ -160,21 +159,19 @@ async fn eth_call_with_counter() { #[tokio::test] async fn eth_call_with_counter_transactions() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default() .with_multicall3_contract() .with_counter_contract(0) .build(); - let multicall = alice.multicall_with_value( - 0.into(), - &[ - alice.create_counter_tx(1.into(), false).into(), - Call3Value::from(alice.create_counter_tx(2.into(), true)).allow_failure(), - alice.query_counter_value().into(), - alice.create_counter_tx(3.into(), false).into(), - ], - ); + let calls = &[ + alice.create_counter_tx(1.into(), false).into(), + Call3Value::from(alice.create_counter_tx(2.into(), true)).allow_failure(), + alice.query_counter_value().into(), + alice.create_counter_tx(3.into(), false).into(), + ]; + let multicall = alice.multicall_with_value(0.into(), calls); let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let output = test_call(&tx_sender, state_override, multicall) @@ -203,7 +200,7 @@ async fn eth_call_with_counter_transactions() { #[tokio::test] async fn eth_call_out_of_gas() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default() .with_infinite_loop_contract() .build(); @@ -219,7 +216,7 @@ async fn eth_call_out_of_gas() { #[tokio::test] async fn eth_call_with_load_test_transactions() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_load_test_contract().build(); let pool = ConnectionPool::::constrained_test_pool(1).await; diff --git a/core/node/api_server/src/tx_sender/tests/gas_estimation.rs b/core/node/api_server/src/tx_sender/tests/gas_estimation.rs index 954792f915cc..c6b2228d5455 100644 --- a/core/node/api_server/src/tx_sender/tests/gas_estimation.rs +++ b/core/node/api_server/src/tx_sender/tests/gas_estimation.rs @@ -5,11 +5,11 @@ use std::collections::HashMap; use assert_matches::assert_matches; use test_casing::{test_casing, Product}; use zksync_system_constants::CODE_ORACLE_ADDRESS; +use zksync_test_contracts::Account; use zksync_types::{ api::state_override::{OverrideAccount, OverrideState}, bytecode::BytecodeHash, web3::keccak256, - K256PrivateKey, }; use super::*; @@ -27,7 +27,7 @@ async fn initial_gas_estimation_is_somewhat_accurate() { let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let transfer_value = U256::from(1_000_000_000); let account_overrides = OverrideAccount { balance: Some(transfer_value * 2), @@ -66,7 +66,7 @@ async fn initial_gas_estimation_is_somewhat_accurate() { #[test_casing(5, LOAD_TEST_CASES)] #[tokio::test] async fn initial_estimate_for_load_test_transaction(tx_params: LoadnextContractExecutionParams) { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); // Set the array length in the load test contract to 100, so that reads don't fail. let state_override = StateBuilder::default().with_load_test_contract().build(); let tx = alice.create_load_test_tx(tx_params); @@ -76,7 +76,7 @@ async fn initial_estimate_for_load_test_transaction(tx_params: LoadnextContractE #[tokio::test] async fn initial_gas_estimate_for_l1_transaction() { - let alice = K256PrivateKey::random(); + let alice = Account::random(); let state_override = StateBuilder::default().with_counter_contract(0).build(); let tx = alice.create_l1_counter_tx(1.into(), false); @@ -99,7 +99,7 @@ async fn initial_gas_estimate_for_l1_transaction() { #[test_casing(2, [false, true])] #[tokio::test] async fn initial_estimate_for_deep_recursion(with_reads: bool) { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_load_test_contract().build(); // Reads are chosen because they represent the worst case. Reads don't influence the amount of pubdata; @@ -132,7 +132,7 @@ async fn initial_estimate_for_deep_recursion(with_reads: bool) { #[tokio::test] async fn initial_estimate_for_deep_recursion_with_large_bytecode() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default() .with_load_test_contract() .inflate_bytecode(StateBuilder::LOAD_TEST_ADDRESS, 50_000) @@ -189,7 +189,7 @@ async fn test_initial_estimate_error(state_override: StateOverride, tx: L2Tx) -> #[test_casing(4, [10, 50, 200, 1_000])] #[tokio::test] async fn initial_estimate_for_expensive_contract(write_count: usize) { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let mut state_override = StateBuilder::default().with_expensive_contract().build(); let tx = alice.create_expensive_tx(write_count); @@ -212,7 +212,7 @@ async fn initial_estimate_for_expensive_contract(write_count: usize) { #[tokio::test] async fn initial_estimate_for_code_oracle_tx() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); // Add another contract that is never executed, but has a large bytecode. let huge_contact_address = Address::repeat_byte(23); let huge_contract_bytecode = vec![0_u8; 10_001 * 32]; @@ -270,7 +270,7 @@ async fn initial_estimate_for_code_oracle_tx() { #[tokio::test] async fn initial_estimate_with_large_free_bytecode() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default() .with_precompiles_contract() .inflate_bytecode(StateBuilder::PRECOMPILES_CONTRACT_ADDRESS, 50_000) @@ -289,7 +289,7 @@ async fn initial_estimate_with_large_free_bytecode() { #[tokio::test] async fn revert_during_initial_estimate() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_counter_contract(0).build(); let tx = alice.create_counter_tx(1.into(), true); @@ -302,7 +302,7 @@ async fn revert_during_initial_estimate() { #[tokio::test] async fn out_of_gas_during_initial_estimate() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default() .with_infinite_loop_contract() .build(); @@ -319,7 +319,7 @@ async fn insufficient_funds_error_for_transfer() { let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let transferred_value = 1_000_000_000.into(); let tx = alice.create_transfer(transferred_value); let fee_scale_factor = 1.0; @@ -394,7 +394,7 @@ async fn test_estimating_gas( #[test_casing(3, [0, 100, 1_000])] #[tokio::test] async fn estimating_gas_for_transfer(acceptable_overestimation: u64) { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let transfer_value = 1_000_000_000.into(); let account_overrides = OverrideAccount { balance: Some(transfer_value * 2), @@ -408,7 +408,7 @@ async fn estimating_gas_for_transfer(acceptable_overestimation: u64) { #[tokio::test] async fn estimating_gas_for_l1_transaction() { - let alice = K256PrivateKey::random(); + let alice = Account::random(); let state_override = StateBuilder::default().with_counter_contract(0).build(); let tx = alice.create_l1_counter_tx(1.into(), false); @@ -421,7 +421,7 @@ async fn estimating_gas_for_load_test_tx( tx_params: LoadnextContractExecutionParams, acceptable_overestimation: u64, ) { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_load_test_contract().build(); let tx = alice.create_load_test_tx(tx_params); @@ -431,7 +431,7 @@ async fn estimating_gas_for_load_test_tx( #[test_casing(4, [10, 50, 100, 200])] #[tokio::test] async fn estimating_gas_for_expensive_txs(write_count: usize) { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_expensive_contract().build(); let tx = alice.create_expensive_tx(write_count); @@ -440,7 +440,7 @@ async fn estimating_gas_for_expensive_txs(write_count: usize) { #[tokio::test] async fn estimating_gas_for_code_oracle_tx() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); // Add another contract that is never executed, but has a large bytecode. let huge_contact_address = Address::repeat_byte(23); let huge_contract_bytecode = vec![0_u8; 10_001 * 32]; @@ -458,7 +458,7 @@ async fn estimating_gas_for_code_oracle_tx() { #[tokio::test] async fn estimating_gas_for_reverting_tx() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default().with_counter_contract(0).build(); let tx = alice.create_counter_tx(1.into(), true); @@ -486,7 +486,7 @@ async fn estimating_gas_for_reverting_tx() { #[tokio::test] async fn estimating_gas_for_infinite_loop_tx() { - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let state_override = StateBuilder::default() .with_infinite_loop_contract() .build(); diff --git a/core/node/api_server/src/tx_sender/tests/send_tx.rs b/core/node/api_server/src/tx_sender/tests/send_tx.rs index c0b02e45ad89..ecf8e7bfd583 100644 --- a/core/node/api_server/src/tx_sender/tests/send_tx.rs +++ b/core/node/api_server/src/tx_sender/tests/send_tx.rs @@ -8,7 +8,7 @@ use test_casing::test_casing; use zksync_multivm::interface::{tracer::ValidationTraces, ExecutionResult}; use zksync_node_fee_model::{BatchFeeModelInputProvider, MockBatchFeeParamsProvider}; use zksync_node_test_utils::create_l2_transaction; -use zksync_types::K256PrivateKey; +use zksync_test_contracts::Account; use super::*; use crate::testonly::{StateBuilder, TestAccount}; @@ -191,7 +191,7 @@ async fn sending_transfer() { let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); // Manually set sufficient balance for the tx initiator. let mut storage = tx_sender.acquire_replica_connection().await.unwrap(); @@ -212,7 +212,7 @@ async fn sending_transfer_with_insufficient_balance() { let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let transfer_value = 1_000_000_000.into(); let transfer = alice.create_transfer(transfer_value); @@ -229,7 +229,7 @@ async fn sending_transfer_with_incorrect_signature() { let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let transfer_value = 1_000_000_000.into(); let mut storage = tx_sender.acquire_replica_connection().await.unwrap(); @@ -251,7 +251,7 @@ async fn sending_load_test_transaction(tx_params: LoadnextContractExecutionParam let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let mut storage = tx_sender.acquire_replica_connection().await.unwrap(); StateBuilder::default() @@ -272,7 +272,7 @@ async fn sending_reverting_transaction() { let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let mut storage = tx_sender.acquire_replica_connection().await.unwrap(); StateBuilder::default() @@ -295,7 +295,7 @@ async fn sending_transaction_out_of_gas() { let pool = ConnectionPool::::constrained_test_pool(1).await; let tx_sender = create_real_tx_sender(pool).await; let block_args = pending_block_args(&tx_sender).await; - let alice = K256PrivateKey::random(); + let mut alice = Account::random(); let mut storage = tx_sender.acquire_replica_connection().await.unwrap(); StateBuilder::default() diff --git a/core/node/api_server/src/utils.rs b/core/node/api_server/src/utils.rs index 6769e773dc77..98b8ca770ba4 100644 --- a/core/node/api_server/src/utils.rs +++ b/core/node/api_server/src/utils.rs @@ -2,11 +2,22 @@ use std::{ cell::Cell, + collections::{HashMap, HashSet}, thread, time::{Duration, Instant}, }; -use zksync_dal::{Connection, Core, DalError}; +use zksync_dal::{ + transactions_web3_dal::ExtendedTransactionReceipt, Connection, Core, CoreDal, DalError, +}; +use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; +use zksync_types::{ + api::TransactionReceipt, + get_code_key, get_nonce_key, h256_to_u256, + tx::execute::DeploymentParams, + utils::{decompose_full_nonce, deployed_address_create, deployed_address_evm_create}, + Address, L2BlockNumber, +}; use zksync_web3_decl::error::Web3Error; /// Opens a readonly transaction over the specified connection. @@ -66,3 +77,463 @@ macro_rules! report_filter { ReportFilter::new($interval, &LAST_TIMESTAMP) }}; } + +/// Fills `contract_address` for the provided transaction receipts. This field must be set +/// only for canonical deployment transactions, i.e., txs with `to == None` for EVM contract deployment +/// and calls to `ContractDeployer.{create, create2, createAccount, create2Account}` for EraVM contracts. +/// Also, it must be set regardless of whether the deployment succeeded (thus e.g. we cannot rely on `ContractDeployed` events +/// emitted by `ContractDeployer` for EraVM contracts). +/// +/// `contract_address` is not set if `from` is a custom account since custom accounts may customize nonce increment policies +/// and/or other deployment aspects. +/// +/// Requires all `receipts` to be from the same L2 block. +pub(crate) async fn fill_transaction_receipts( + storage: &mut Connection<'_, Core>, + mut receipts: Vec, +) -> Result, Web3Error> { + receipts.sort_unstable_by_key(|receipt| receipt.inner.transaction_index); + + let mut deployments = Vec::with_capacity(receipts.len()); + for receipt in &receipts { + deployments.push(if receipt.inner.to.is_none() { + Some(DeploymentTransactionType::Evm) + } else if receipt.inner.to == Some(CONTRACT_DEPLOYER_ADDRESS) { + DeploymentParams::decode(&receipt.calldata.0)?.map(DeploymentTransactionType::EraVm) + } else { + // Not a deployment transaction. + None + }); + } + + // Get the AA type for deployment transactions to filter out custom AAs below. + let deployment_receipts = receipts + .iter() + .zip(&deployments) + .filter_map(|(receipt, deployment)| deployment.is_some().then_some(receipt)); + let account_types = if let Some(first_receipt) = deployment_receipts.clone().next() { + let block_number = L2BlockNumber(first_receipt.inner.block_number.as_u32()); + let from_addresses = deployment_receipts.map(|receipt| receipt.inner.from); + get_account_types(storage, from_addresses, block_number).await? + } else { + HashMap::new() + }; + + let mut filled_receipts = Vec::with_capacity(receipts.len()); + let mut receipt_indexes_with_unknown_nonce = HashSet::new(); + for (i, (mut receipt, mut deployment)) in receipts.into_iter().zip(deployments).enumerate() { + if deployment.is_some() && matches!(account_types[&receipt.inner.from], AccountType::Custom) + { + // Custom AAs may interpret transaction data in an arbitrary way (or, more realistically, use a custom + // nonce increment scheme). Hence, we don't even try to assign `contract_address` for a receipt from a custom AA. + deployment = None; + } + + receipt.inner.contract_address = deployment.and_then(|deployment| match deployment { + DeploymentTransactionType::Evm => Some(deployed_address_evm_create( + receipt.inner.from, + receipt.nonce, + )), + DeploymentTransactionType::EraVm( + DeploymentParams::Create | DeploymentParams::CreateAccount, + ) => { + // We need a deployment nonce which isn't available locally; we'll compute it in a single batch below. + receipt_indexes_with_unknown_nonce.insert(i); + None + } + DeploymentTransactionType::EraVm( + DeploymentParams::Create2(data) | DeploymentParams::Create2Account(data), + ) => Some(data.derive_address(receipt.inner.from)), + }); + filled_receipts.push(receipt.inner); + } + + if let Some(&first_idx) = receipt_indexes_with_unknown_nonce.iter().next() { + let block_number = L2BlockNumber(filled_receipts[first_idx].block_number.as_u32()); + // We cannot iterate over `receipt_indexes_with_unknown_nonce` since it would violate Rust aliasing rules + // (Rust isn't smart enough to understand that `receipt_indexes_with_unknown_nonce` are unique). + let unknown_receipts = filled_receipts + .iter_mut() + .enumerate() + .filter_map(|(i, receipt)| { + receipt_indexes_with_unknown_nonce + .contains(&i) + .then_some(receipt) + }) + .collect(); + fill_receipts_with_unknown_nonce(storage, block_number, unknown_receipts).await?; + } + + Ok(filled_receipts) +} + +#[derive(Debug)] +enum DeploymentTransactionType { + Evm, + EraVm(DeploymentParams), +} + +#[derive(Debug, Clone, Copy)] +enum AccountType { + Default, + Custom, +} + +async fn get_account_types( + storage: &mut Connection<'_, Core>, + addresses: impl Iterator, + block_number: L2BlockNumber, +) -> Result, Web3Error> { + let code_keys_to_addresses: HashMap<_, _> = addresses + .map(|from| (get_code_key(&from).hashed_key(), from)) + .collect(); + + // It's fine to query values at the end of the block since the contract type never changes. + let code_keys: Vec<_> = code_keys_to_addresses.keys().copied().collect(); + let storage_values = storage + .storage_logs_dal() + .get_storage_values(&code_keys, block_number) + .await + .map_err(DalError::generalize)?; + + Ok(code_keys_to_addresses + .into_iter() + .map(|(code_key, address)| { + let value = storage_values + .get(&code_key) + .copied() + .flatten() + .unwrap_or_default(); + // The code key slot is non-zero for custom AAs + let account_type = if value.is_zero() { + AccountType::Default + } else { + AccountType::Custom + }; + (address, account_type) + }) + .collect()) +} + +async fn fill_receipts_with_unknown_nonce( + storage: &mut Connection<'_, Core>, + block_number: L2BlockNumber, + receipts: Vec<&mut TransactionReceipt>, +) -> Result<(), Web3Error> { + // Load nonces at the start of the block. + let nonce_keys: Vec<_> = receipts + .iter() + .map(|receipt| get_nonce_key(&receipt.from).hashed_key()) + .collect(); + let nonces_at_block_start = storage + .storage_logs_dal() + .get_storage_values(&nonce_keys, block_number - 1) + .await + .map_err(DalError::generalize)?; + + // Load deployment events for the block in order to compute deployment nonces at the start of each transaction. + // TODO: can filter by the senders as well if necessary. + let deployment_events = storage + .events_web3_dal() + .get_contract_deployment_logs(block_number) + .await + .map_err(DalError::generalize)?; + + for (receipt, nonce_key) in receipts.into_iter().zip(nonce_keys) { + let sender = receipt.from; + let tx_index = receipt.transaction_index.as_u64(); + let initial_nonce = nonces_at_block_start + .get(&nonce_key) + .copied() + .flatten() + .unwrap_or_default(); + let (_, initial_deploy_nonce) = decompose_full_nonce(h256_to_u256(initial_nonce)); + + let nonce_increment = deployment_events + .iter() + // Can use `take_while` because events are ordered by `transaction_index_in_block`. + .take_while(|event| event.transaction_index_in_block < tx_index) + .filter(|event| event.deployer == sender) + .count(); + let deploy_nonce = initial_deploy_nonce + nonce_increment; + receipt.contract_address = Some(deployed_address_create(sender, deploy_nonce)); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use zksync_dal::{events_web3_dal::ContractDeploymentLog, ConnectionPool}; + use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; + use zksync_test_contracts::{Account, TestContract, TxType}; + use zksync_types::{ + ethabi, utils::storage_key_for_eth_balance, Address, Execute, ExecuteTransactionCommon, + StorageLog, Transaction, H256, + }; + + use super::*; + use crate::testonly::{ + default_fee, persist_block_with_transactions, StateBuilder, TestAccount, + }; + + async fn prepare_storage(storage: &mut Connection<'_, Core>, rich_account: Address) { + insert_genesis_batch(storage, &GenesisParams::mock()) + .await + .unwrap(); + let balance_key = storage_key_for_eth_balance(&rich_account); + let balance_log = StorageLog::new_write_log(balance_key, H256::from_low_u64_be(u64::MAX)); + storage + .storage_logs_dal() + .append_storage_logs(L2BlockNumber(0), &[balance_log]) + .await + .unwrap(); + } + + #[tokio::test] + async fn fill_transaction_receipts_basics() { + let mut alice = Account::random(); + let transfer = alice.create_transfer(1.into()); + let transfer_hash = transfer.hash(); + let deployment = alice + .get_deploy_tx(TestContract::counter().bytecode, None, TxType::L2) + .tx; + let deployment_hash = deployment.hash(); + + let pool = ConnectionPool::test_pool().await; + let mut storage = pool.connection().await.unwrap(); + prepare_storage(&mut storage, alice.address()).await; + persist_block_with_transactions(&pool, vec![transfer.into(), deployment]).await; + + let receipts = storage + .transactions_web3_dal() + .get_transaction_receipts(&[transfer_hash]) + .await + .unwrap(); + assert_eq!(receipts.len(), 1); + let filled_receipts = fill_transaction_receipts(&mut storage, receipts) + .await + .unwrap(); + assert_eq!(filled_receipts.len(), 1); + let transfer_receipt = filled_receipts.into_iter().next().unwrap(); + assert_eq!(transfer_receipt.status, 1.into()); + assert_eq!(transfer_receipt.contract_address, None); + + let receipts = storage + .transactions_web3_dal() + .get_transaction_receipts(&[deployment_hash]) + .await + .unwrap(); + assert_eq!(receipts.len(), 1); + let filled_receipts = fill_transaction_receipts(&mut storage, receipts) + .await + .unwrap(); + assert_eq!(filled_receipts.len(), 1); + let deploy_receipt = filled_receipts.into_iter().next().unwrap(); + assert_eq!(deploy_receipt.status, 1.into()); + assert_eq!( + deploy_receipt.contract_address, + Some(deployed_address_create(alice.address(), 0.into())) + ); + } + + #[tokio::test] + async fn various_deployments() { + let mut alice = Account::random(); + let (create2_execute, create2_params) = Execute::for_create2_deploy( + H256::zero(), + TestContract::counter().bytecode.to_vec(), + &[], + ); + + let txs = vec![ + alice.create_transfer(1.into()).into(), + alice + .get_deploy_tx(TestContract::counter().bytecode, None, TxType::L2) + .tx, + alice.create_transfer(1.into()).into(), + // Failed deployment: this should fail with an out-of-gas error due to allocating too many items for reads + alice + .get_deploy_tx( + TestContract::load_test().bytecode, + Some(&[ethabi::Token::Uint(u64::MAX.into())]), + TxType::L2, + ) + .tx, + alice.get_l2_tx_for_execute(create2_execute.clone(), None), + // Failed deployment: this tries to deploy to the same address as the previous transaction. + alice.get_l2_tx_for_execute(create2_execute, None), + alice + .get_deploy_tx( + TestContract::load_test().bytecode, + Some(&[ethabi::Token::Uint(10.into())]), + TxType::L2, + ) + .tx, + ]; + let tx_hashes: Vec<_> = txs.iter().map(Transaction::hash).collect(); + let expected_statuses_and_contract_addresses = [ + (1_u32, None), + (1, Some(deployed_address_create(alice.address(), 0.into()))), + (1, None), + (0, Some(deployed_address_create(alice.address(), 1.into()))), + (1, Some(create2_params.derive_address(alice.address()))), + (0, Some(create2_params.derive_address(alice.address()))), + // deployment nonce 1 was used by the successful `create2` tx + (1, Some(deployed_address_create(alice.address(), 2.into()))), + ]; + + let pool = ConnectionPool::test_pool().await; + let mut storage = pool.connection().await.unwrap(); + prepare_storage(&mut storage, alice.address()).await; + persist_block_with_transactions(&pool, txs).await; + + // Sanity check: for successful deployments, the actual deployed address must correspond to the derived one. + let deployment_events = storage + .events_web3_dal() + .get_contract_deployment_logs(L2BlockNumber(1)) + .await + .unwrap(); + let expected_events: Vec<_> = expected_statuses_and_contract_addresses + .iter() + .enumerate() + .filter_map(|(i, &(status, address))| { + if let (1, Some(deployed_address)) = (status, address) { + Some(ContractDeploymentLog { + transaction_index_in_block: i as u64, + deployer: alice.address(), + deployed_address, + }) + } else { + None + } + }) + .collect(); + assert_eq!(deployment_events, expected_events); + + for (&tx_hash, &(status, expected_address)) in tx_hashes + .iter() + .zip(&expected_statuses_and_contract_addresses) + { + println!("Fetching receipt for {tx_hash:?}"); + let receipts = storage + .transactions_web3_dal() + .get_transaction_receipts(&[tx_hash]) + .await + .unwrap(); + let filled_receipts = fill_transaction_receipts(&mut storage, receipts) + .await + .unwrap(); + assert_eq!(filled_receipts.len(), 1); + let receipt = filled_receipts.into_iter().next().unwrap(); + assert_eq!(receipt.status, status.into()); + assert_eq!(receipt.contract_address, expected_address); + } + + // Test all receipts for a block. + let receipts = storage + .transactions_web3_dal() + .get_transaction_receipts(&tx_hashes) + .await + .unwrap(); + let filled_receipts = fill_transaction_receipts(&mut storage, receipts) + .await + .unwrap(); + let statuses_and_contract_addresses: Vec<_> = filled_receipts + .iter() + .map(|receipt| (receipt.status.as_u32(), receipt.contract_address)) + .collect(); + assert_eq!( + statuses_and_contract_addresses, + expected_statuses_and_contract_addresses + ); + } + + /// Because of AA support, determining deployment nonces is non-trivial. E.g., it would be incorrect + /// to define a deployment nonce increment as a count of successful deployment txs from an EOA because + /// the account might have a non-default AA that can be called as a contract (so it can deploy contracts + /// outside of deployment txs). + #[tokio::test] + async fn deployments_with_custom_account() { + let mut alice = Account::random(); + let (account_deploy_tx, account_addr) = + alice.create2_account(TestContract::permissive_account().bytecode.to_vec()); + + let pool = ConnectionPool::test_pool().await; + let mut storage = pool.connection().await.unwrap(); + insert_genesis_batch(&mut storage, &GenesisParams::mock()) + .await + .unwrap(); + StateBuilder::default() + .with_balance(alice.address(), u64::MAX.into()) + .apply(&mut storage) + .await; + + let deploy_execute = Execute { + contract_address: Some(account_addr), + calldata: TestContract::permissive_account() + .function("deploy") + .encode_input(&[ethabi::Token::Uint(5.into())]) + .unwrap(), + value: 0.into(), + factory_deps: vec![TestContract::permissive_account().dependencies[0] + .bytecode + .to_vec()], + }; + + let mut txs = vec![ + // Deploy the account + account_deploy_tx.into(), + // Transfer tokens to the created account + alice + .create_transfer_with_fee(account_addr, (u64::MAX / 2).into(), default_fee()) + .into(), + // Trigger deployments from the account by calling it + alice.get_l2_tx_for_execute(deploy_execute, None), + ]; + + // Trigger a deployment from the AA being the tx initiator + let mut deploy_tx_from_account = Account::random() + .get_deploy_tx(TestContract::counter().bytecode, None, TxType::L2) + .tx; + let ExecuteTransactionCommon::L2(data) = &mut deploy_tx_from_account.common_data else { + unreachable!(); + }; + // This invalidates the tx signature, but we don't care because the deployed AA doesn't check it + data.initiator_address = account_addr; + txs.push(deploy_tx_from_account); + + let tx_hashes: Vec<_> = txs.iter().map(Transaction::hash).collect(); + let expected_contract_addresses = [Some(account_addr), None, None, None]; + persist_block_with_transactions(&pool, txs).await; + + // Check the account type helper. + let account_types = get_account_types( + &mut storage, + [alice.address, account_addr].into_iter(), + L2BlockNumber(1), + ) + .await + .unwrap(); + + assert_matches!(account_types[&alice.address], AccountType::Default); + assert_matches!(account_types[&account_addr], AccountType::Custom); + + let receipts = storage + .transactions_web3_dal() + .get_transaction_receipts(&tx_hashes) + .await + .unwrap(); + let filled_receipts = fill_transaction_receipts(&mut storage, receipts) + .await + .unwrap(); + let contract_addresses: Vec<_> = filled_receipts + .iter() + .map(|receipt| { + assert_eq!(receipt.status, 1.into()); + receipt.contract_address + }) + .collect(); + assert_eq!(contract_addresses, expected_contract_addresses); + } +} diff --git a/core/node/api_server/src/web3/namespaces/eth.rs b/core/node/api_server/src/web3/namespaces/eth.rs index f71b66b416e0..7479a9b8e6c8 100644 --- a/core/node/api_server/src/web3/namespaces/eth.rs +++ b/core/node/api_server/src/web3/namespaces/eth.rs @@ -22,7 +22,7 @@ use zksync_web3_decl::{ use crate::{ execution_sandbox::BlockArgs, tx_sender::BinarySearchKind, - utils::open_readonly_transaction, + utils::{fill_transaction_receipts, open_readonly_transaction}, web3::{backend_jsonrpsee::MethodTracer, metrics::API_METRICS, state::RpcState, TypedFilter}, }; @@ -372,12 +372,12 @@ impl EthNamespace { }; self.set_block_diff(block_number); // only report block diff for existing L2 blocks - let mut receipts = storage + let receipts = storage .transactions_web3_dal() .get_transaction_receipts(&block.transactions) .await .with_context(|| format!("get_transaction_receipts({block_number})"))?; - receipts.sort_unstable_by_key(|receipt| receipt.transaction_index); + let receipts = fill_transaction_receipts(&mut storage, receipts).await?; Ok(Some(receipts)) } @@ -554,6 +554,7 @@ impl EthNamespace { .get_transaction_receipts(&[hash]) .await .context("get_transaction_receipts")?; + let receipts = fill_transaction_receipts(&mut storage, receipts).await?; Ok(receipts.into_iter().next()) } diff --git a/core/node/api_server/src/web3/tests/debug.rs b/core/node/api_server/src/web3/tests/debug.rs index b2aae53eaa32..12a17332e848 100644 --- a/core/node/api_server/src/web3/tests/debug.rs +++ b/core/node/api_server/src/web3/tests/debug.rs @@ -32,7 +32,7 @@ fn execute_l2_transaction_with_traces(index_in_block: u8) -> TransactionExecutio }; TransactionExecutionResult { call_traces: vec![first_call_trace, second_call_trace], - ..execute_l2_transaction(create_l2_transaction(1, 2)) + ..mock_execute_transaction(create_l2_transaction(1, 2).into()) } } diff --git a/core/node/api_server/src/web3/tests/filters.rs b/core/node/api_server/src/web3/tests/filters.rs index c865526815d1..c3e31fa2b205 100644 --- a/core/node/api_server/src/web3/tests/filters.rs +++ b/core/node/api_server/src/web3/tests/filters.rs @@ -38,7 +38,7 @@ impl HttpTest for BasicFilterChangesTest { // Sleep a little so that the filter timestamp is strictly lesser than the transaction "received at" timestamp. tokio::time::sleep(POLL_INTERVAL).await; - let tx_result = execute_l2_transaction(create_l2_transaction(1, 2)); + let tx_result = mock_execute_transaction(create_l2_transaction(1, 2).into()); let new_tx_hash = tx_result.hash; let new_l2_block = store_l2_block( &mut pool.connection().await?, diff --git a/core/node/api_server/src/web3/tests/mod.rs b/core/node/api_server/src/web3/tests/mod.rs index 25405b50c508..8035b8645dc7 100644 --- a/core/node/api_server/src/web3/tests/mod.rs +++ b/core/node/api_server/src/web3/tests/mod.rs @@ -17,10 +17,9 @@ use zksync_config::{ GenesisConfig, }; use zksync_contracts::BaseSystemContracts; -use zksync_dal::{transactions_dal::L2TxSubmissionResult, Connection, ConnectionPool, CoreDal}; +use zksync_dal::{Connection, ConnectionPool, CoreDal}; use zksync_multivm::interface::{ - tracer::ValidationTraces, TransactionExecutionMetrics, TransactionExecutionResult, - TxExecutionStatus, VmEvent, VmExecutionMetrics, + tracer::ValidationTraces, TransactionExecutionMetrics, TransactionExecutionResult, VmEvent, }; use zksync_node_genesis::{insert_genesis_batch, mock_genesis_config, GenesisParams}; use zksync_node_test_utils::{ @@ -39,15 +38,13 @@ use zksync_types::{ }, fee_model::{BatchFeeInput, FeeParams}, get_nonce_key, - l2::L2Tx, storage::get_code_key, system_contracts::get_system_smart_contracts, tokens::{TokenInfo, TokenMetadata}, tx::IncludedTxLocation, u256_to_h256, utils::{storage_key_for_eth_balance, storage_key_for_standard_token_balance}, - AccountTreeId, Address, L1BatchNumber, Nonce, ProtocolVersionId, StorageKey, StorageLog, H256, - U256, U64, + AccountTreeId, Address, L1BatchNumber, Nonce, StorageKey, StorageLog, H256, U256, U64, }; use zksync_vm_executor::oneshot::MockOneshotExecutor; use zksync_web3_decl::{ @@ -65,7 +62,11 @@ use zksync_web3_decl::{ }; use super::*; -use crate::{tx_sender::SandboxExecutorOptions, web3::testonly::TestServerBuilder}; +use crate::{ + testonly::{mock_execute_transaction, store_custom_l2_block}, + tx_sender::SandboxExecutorOptions, + web3::testonly::TestServerBuilder, +}; mod debug; mod filters; @@ -326,20 +327,6 @@ fn assert_logs_match(actual_logs: &[api::Log], expected_logs: &[&VmEvent]) { } } -fn execute_l2_transaction(transaction: L2Tx) -> TransactionExecutionResult { - TransactionExecutionResult { - hash: transaction.hash(), - transaction: transaction.into(), - execution_info: VmExecutionMetrics::default(), - execution_status: TxExecutionStatus::Success, - refunded_gas: 0, - operator_suggested_refund: 0, - compressed_bytecodes: vec![], - call_traces: vec![], - revert_reason: None, - } -} - /// Stores L2 block and returns the L2 block header. async fn store_l2_block( storage: &mut Connection<'_, Core>, @@ -351,52 +338,6 @@ async fn store_l2_block( Ok(header) } -async fn store_custom_l2_block( - storage: &mut Connection<'_, Core>, - header: &L2BlockHeader, - transaction_results: &[TransactionExecutionResult], -) -> anyhow::Result<()> { - let number = header.number; - for result in transaction_results { - let l2_tx = result.transaction.clone().try_into().unwrap(); - let tx_submission_result = storage - .transactions_dal() - .insert_transaction_l2( - &l2_tx, - TransactionExecutionMetrics::default(), - ValidationTraces::default(), - ) - .await - .unwrap(); - assert_matches!(tx_submission_result, L2TxSubmissionResult::Added); - } - - // Record L2 block info which is read by the VM sandbox logic - let l2_block_info_key = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - ); - let block_info = pack_block_info(number.0.into(), number.0.into()); - let l2_block_log = StorageLog::new_write_log(l2_block_info_key, u256_to_h256(block_info)); - storage - .storage_logs_dal() - .append_storage_logs(number, &[l2_block_log]) - .await?; - - storage.blocks_dal().insert_l2_block(header).await?; - storage - .transactions_dal() - .mark_txs_as_executed_in_l2_block( - number, - transaction_results, - 1.into(), - ProtocolVersionId::latest(), - false, - ) - .await?; - Ok(()) -} - async fn open_l1_batch( storage: &mut Connection<'_, Core>, number: L1BatchNumber, @@ -763,7 +704,7 @@ impl HttpTest for TransactionCountTest { store_l2_block( &mut storage, l2_block_number, - &[execute_l2_transaction(committed_tx)], + &[mock_execute_transaction(committed_tx.into())], ) .await?; let nonce_log = StorageLog::new_write_log( @@ -927,8 +868,8 @@ impl HttpTest for TransactionReceiptsTest { let tx1 = create_l2_transaction(10, 200); let tx2 = create_l2_transaction(10, 200); let tx_results = vec![ - execute_l2_transaction(tx1.clone()), - execute_l2_transaction(tx2.clone()), + mock_execute_transaction(tx1.clone().into()), + mock_execute_transaction(tx2.clone().into()), ]; store_l2_block(&mut storage, l2_block_number, &tx_results).await?; diff --git a/core/node/api_server/src/web3/tests/snapshots.rs b/core/node/api_server/src/web3/tests/snapshots.rs index 46261c8c6fb7..76ffd086144d 100644 --- a/core/node/api_server/src/web3/tests/snapshots.rs +++ b/core/node/api_server/src/web3/tests/snapshots.rs @@ -37,7 +37,7 @@ impl HttpTest for SnapshotBasicsTest { store_l2_block( &mut storage, L2BlockNumber(1), - &[execute_l2_transaction(create_l2_transaction(1, 2))], + &[mock_execute_transaction(create_l2_transaction(1, 2).into())], ) .await?; seal_l1_batch(&mut storage, L1BatchNumber(1)).await?; diff --git a/core/node/api_server/src/web3/tests/ws.rs b/core/node/api_server/src/web3/tests/ws.rs index 008747a63bcc..7720d6763056 100644 --- a/core/node/api_server/src/web3/tests/ws.rs +++ b/core/node/api_server/src/web3/tests/ws.rs @@ -265,7 +265,7 @@ impl WsTest for BasicSubscriptionsTest { wait_for_subscription(&mut pub_sub_events, SubscriptionType::Txs).await; let mut storage = pool.connection().await?; - let tx_result = execute_l2_transaction(create_l2_transaction(1, 2)); + let tx_result = mock_execute_transaction(create_l2_transaction(1, 2).into()); let new_tx_hash = tx_result.hash; let l2_block_number = if self.snapshot_recovery { StorageInitialization::SNAPSHOT_RECOVERY_BLOCK + 2 diff --git a/core/node/node_sync/src/tests.rs b/core/node/node_sync/src/tests.rs index 21058144f778..1db04902e176 100644 --- a/core/node/node_sync/src/tests.rs +++ b/core/node/node_sync/src/tests.rs @@ -259,14 +259,15 @@ async fn external_io_basics(snapshot_recovery: bool) { assert_eq!(l2_block.l1_tx_count, 0); assert_eq!(l2_block.l2_tx_count, 1); - let tx_receipt = storage + let tx_receipts = storage .transactions_web3_dal() .get_transaction_receipts(&[tx_hash]) .await - .unwrap() + .unwrap(); + let tx_receipt = &tx_receipts .first() - .cloned() - .expect("Transaction not persisted"); + .expect("Transaction not persisted") + .inner; assert_eq!( tx_receipt.block_number, (snapshot.l2_block_number.0 + 1).into() diff --git a/core/node/state_keeper/src/executor/tests/tester.rs b/core/node/state_keeper/src/executor/tests/tester.rs index 6c5015fbca46..dc2791833e64 100644 --- a/core/node/state_keeper/src/executor/tests/tester.rs +++ b/core/node/state_keeper/src/executor/tests/tester.rs @@ -18,7 +18,7 @@ use zksync_multivm::{ vm_latest::constants::INITIAL_STORAGE_WRITE_PUBDATA_BYTES, }; use zksync_node_genesis::create_genesis_l1_batch; -use zksync_node_test_utils::{recover, Snapshot}; +use zksync_node_test_utils::{default_l1_batch_env, default_system_env, recover, Snapshot}; use zksync_state::{OwnedStorage, ReadStorageFactory, RocksdbStorageOptions}; use zksync_test_contracts::{ Account, DeployContractsTx, LoadnextContractExecutionParams, TestContract, TxType, @@ -43,7 +43,6 @@ use zksync_vm_executor::batch::{MainBatchExecutorFactory, TraceCalls}; use super::{read_storage_factory::RocksdbStorageFactory, StorageType}; use crate::{ testonly::{self, apply_genesis_logs, BASE_SYSTEM_CONTRACTS}, - tests::{default_l1_batch_env, default_system_env}, AsyncRocksdbCache, }; diff --git a/core/node/state_keeper/src/io/persistence.rs b/core/node/state_keeper/src/io/persistence.rs index 8db7fe4120ed..d1f3b9f36bb5 100644 --- a/core/node/state_keeper/src/io/persistence.rs +++ b/core/node/state_keeper/src/io/persistence.rs @@ -385,6 +385,7 @@ mod tests { use zksync_dal::CoreDal; use zksync_multivm::interface::{FinishedL1Batch, VmExecutionMetrics}; use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; + use zksync_node_test_utils::{default_l1_batch_env, default_system_env}; use zksync_types::{ api::TransactionStatus, h256_to_u256, writes::StateDiffRecord, L1BatchNumber, L2BlockNumber, StorageLogKind, H256, U256, @@ -393,10 +394,7 @@ mod tests { use super::*; use crate::{ io::L2BlockParams, - tests::{ - create_execution_result, create_transaction, create_updates_manager, - default_l1_batch_env, default_system_env, Query, - }, + tests::{create_execution_result, create_transaction, create_updates_manager, Query}, OutputHandler, }; diff --git a/core/node/state_keeper/src/io/seal_logic/l2_block_seal_subtasks.rs b/core/node/state_keeper/src/io/seal_logic/l2_block_seal_subtasks.rs index 701e121285d4..a4b0ee3bde8a 100644 --- a/core/node/state_keeper/src/io/seal_logic/l2_block_seal_subtasks.rs +++ b/core/node/state_keeper/src/io/seal_logic/l2_block_seal_subtasks.rs @@ -594,14 +594,12 @@ mod tests { // Check DAL doesn't return tx receipt before block header is saved. let mut connection = pool.connection().await.unwrap(); - let tx_receipt = connection + let tx_receipts = connection .transactions_web3_dal() .get_transaction_receipts(&[tx_hash]) .await - .unwrap() - .first() - .cloned(); - assert!(tx_receipt.is_none()); + .unwrap(); + assert!(tx_receipts.is_empty(), "{tx_receipts:?}"); // Insert block header. let l2_block_header = L2BlockHeader { @@ -638,7 +636,8 @@ mod tests { .get_transaction_receipts(&[tx_hash]) .await .unwrap() - .remove(0); + .remove(0) + .inner; assert_eq!(tx_receipt.block_number.as_u32(), 1); assert_eq!(tx_receipt.logs.len(), 1); assert_eq!(tx_receipt.l2_to_l1_logs.len(), 1); diff --git a/core/node/state_keeper/src/io/seal_logic/mod.rs b/core/node/state_keeper/src/io/seal_logic/mod.rs index 475be3319efa..fe43d323b88f 100644 --- a/core/node/state_keeper/src/io/seal_logic/mod.rs +++ b/core/node/state_keeper/src/io/seal_logic/mod.rs @@ -11,10 +11,7 @@ use itertools::Itertools; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; use zksync_multivm::{ interface::{DeduplicatedWritesMetrics, TransactionExecutionResult, VmEvent}, - utils::{ - get_max_batch_gas_limit, get_max_gas_per_pubdata_byte, ModifiedSlot, - StorageWritesDeduplicator, - }, + utils::{get_max_batch_gas_limit, get_max_gas_per_pubdata_byte, StorageWritesDeduplicator}, }; use zksync_shared_metrics::{BlockStage, L2BlockStage, APP_METRICS}; use zksync_types::{ @@ -443,19 +440,12 @@ impl L2BlockSealCommand { } fn extract_deduplicated_write_logs(&self) -> Vec { - let mut storage_writes_deduplicator = StorageWritesDeduplicator::new(); - storage_writes_deduplicator.apply( + StorageWritesDeduplicator::deduplicate_logs( self.l2_block .storage_logs .iter() .filter(|log| log.log.is_write()), - ); - let deduplicated_logs = storage_writes_deduplicator.into_modified_key_values(); - - deduplicated_logs - .into_iter() - .map(|(key, ModifiedSlot { value, .. })| StorageLog::new_write_log(key, value)) - .collect() + ) } fn transaction(&self, index: usize) -> &Transaction { diff --git a/core/node/state_keeper/src/tests/mod.rs b/core/node/state_keeper/src/tests/mod.rs index e235cddf8423..00e57e02e214 100644 --- a/core/node/state_keeper/src/tests/mod.rs +++ b/core/node/state_keeper/src/tests/mod.rs @@ -10,18 +10,17 @@ use tokio::sync::watch; use zksync_config::configs::chain::StateKeeperConfig; use zksync_multivm::{ interface::{ - Halt, L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionLogs, - VmExecutionResultAndLogs, VmExecutionStatistics, + Halt, SystemEnv, TxExecutionMode, VmExecutionLogs, VmExecutionResultAndLogs, + VmExecutionStatistics, }, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; -use zksync_node_test_utils::create_l2_transaction; +use zksync_node_test_utils::{create_l2_transaction, default_l1_batch_env, default_system_env}; use zksync_types::{ block::{L2BlockExecutionData, L2BlockHasher}, - fee_model::{BatchFeeInput, PubdataIndependentBatchFeeModelInput}, u256_to_h256, AccountTreeId, Address, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, StorageKey, StorageLog, StorageLogKind, StorageLogWithPreviousValue, - Transaction, H256, U256, ZKPORTER_IS_AVAILABLE, + Transaction, H256, U256, }; use crate::{ @@ -65,43 +64,6 @@ pub(crate) fn pending_batch_data(pending_l2_blocks: Vec) - } } -pub(super) fn default_system_env() -> SystemEnv { - SystemEnv { - zk_porter_available: ZKPORTER_IS_AVAILABLE, - version: ProtocolVersionId::latest(), - base_system_smart_contracts: BASE_SYSTEM_CONTRACTS.clone(), - bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - execution_mode: TxExecutionMode::VerifyExecute, - default_validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - chain_id: L2ChainId::from(270), - } -} - -pub(super) fn default_l1_batch_env( - number: u32, - timestamp: u64, - fee_account: Address, -) -> L1BatchEnv { - L1BatchEnv { - previous_batch_hash: None, - number: L1BatchNumber(number), - timestamp, - fee_account, - enforced_base_fee: None, - first_l2_block: L2BlockEnv { - number, - timestamp, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(number - 1)), - max_virtual_blocks_to_create: 1, - }, - fee_input: BatchFeeInput::PubdataIndependent(PubdataIndependentBatchFeeModelInput { - fair_l2_gas_price: 1, - fair_pubdata_price: 1, - l1_gas_price: 1, - }), - } -} - pub(super) fn create_updates_manager() -> UpdatesManager { let l1_batch_env = default_l1_batch_env(1, 1, Address::default()); UpdatesManager::new(&l1_batch_env, &default_system_env(), Default::default()) diff --git a/core/node/test_utils/src/lib.rs b/core/node/test_utils/src/lib.rs index ac900e72bb6b..8805c6eabd97 100644 --- a/core/node/test_utils/src/lib.rs +++ b/core/node/test_utils/src/lib.rs @@ -7,13 +7,13 @@ use zksync_dal::{Connection, Core, CoreDal}; use zksync_merkle_tree::{domain::ZkSyncTree, TreeInstruction}; use zksync_system_constants::{get_intrinsic_constants, ZKPORTER_IS_AVAILABLE}; use zksync_types::{ - block::{L1BatchHeader, L2BlockHeader}, + block::{L1BatchHeader, L2BlockHasher, L2BlockHeader}, commitment::{ AuxCommitments, L1BatchCommitmentArtifacts, L1BatchCommitmentHash, L1BatchMetaParameters, L1BatchMetadata, }, fee::Fee, - fee_model::BatchFeeInput, + fee_model::{BatchFeeInput, PubdataIndependentBatchFeeModelInput}, l2::L2Tx, l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, protocol_version::ProtocolSemanticVersion, @@ -22,11 +22,49 @@ use zksync_types::{ Address, K256PrivateKey, L1BatchNumber, L2BlockNumber, L2ChainId, Nonce, ProtocolVersion, ProtocolVersionId, StorageLog, H256, U256, }; -use zksync_vm_interface::{TransactionExecutionResult, TxExecutionStatus, VmExecutionMetrics}; +use zksync_vm_interface::{ + L1BatchEnv, L2BlockEnv, SystemEnv, TransactionExecutionResult, TxExecutionMode, + TxExecutionStatus, VmExecutionMetrics, +}; /// Value for recent protocol versions. const MAX_GAS_PER_PUBDATA_BYTE: u64 = 50_000; +/// Creates a mock system env with reasonable params. +pub fn default_system_env() -> SystemEnv { + SystemEnv { + zk_porter_available: ZKPORTER_IS_AVAILABLE, + version: ProtocolVersionId::latest(), + base_system_smart_contracts: BaseSystemContracts::load_from_disk(), + bootloader_gas_limit: u32::MAX, + execution_mode: TxExecutionMode::VerifyExecute, + default_validation_computational_gas_limit: u32::MAX, + chain_id: L2ChainId::from(270), + } +} + +/// Creates a mock L1 batch env with reasonable params. +pub fn default_l1_batch_env(number: u32, timestamp: u64, fee_account: Address) -> L1BatchEnv { + L1BatchEnv { + previous_batch_hash: None, + number: L1BatchNumber(number), + timestamp, + fee_account, + enforced_base_fee: None, + first_l2_block: L2BlockEnv { + number, + timestamp, + prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(number - 1)), + max_virtual_blocks_to_create: 1, + }, + fee_input: BatchFeeInput::PubdataIndependent(PubdataIndependentBatchFeeModelInput { + fair_l2_gas_price: 1, + fair_pubdata_price: 1, + l1_gas_price: 1, + }), + } +} + /// Creates an L2 block header with the specified number and deterministic contents. pub fn create_l2_block(number: u32) -> L2BlockHeader { L2BlockHeader {