From 293617ac8b952f2a479a4306e15a8f828757eaad Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Thu, 16 Jan 2025 11:39:42 +0000 Subject: [PATCH 01/52] refactor: move Cargo.toml from root to core workspace (#3456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Move `Cargo.toml` from repository root to `core` workspace. ## Why ❔ It is mandatory for proper separation of workspaces and setting up a `release-please` CI that requires all files of a component to be inside the component directory. ## CI failures Currently, the following tests are failing: * `protobuf_compatibility` - the workflow is using base version and will be fixed with the next PRs after merge to `main`. ## 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`. --- .dockerignore | 4 +- .githooks/pre-commit | 8 +- .github/workflows/cargo-license.yaml | 1 + .github/workflows/ci-core-reusable.yml | 2 +- .github/workflows/ci.yml | 4 +- .github/workflows/protobuf.yaml | 20 +- .github/workflows/vm-perf-comparison.yml | 12 +- .gitignore | 6 - Cargo.toml | 326 ------------------ Cargo.lock => core/Cargo.lock | 0 core/Cargo.toml | 326 ++++++++++++++++++ .../system-constants-generator/src/main.rs | 2 +- deny.toml => core/deny.toml | 0 .../lib/contract_verifier/src/resolver/env.rs | 2 +- core/lib/contracts/src/lib.rs | 2 +- core/lib/utils/src/env.rs | 34 +- core/tests/loadnext/src/fs_utils.rs | 2 +- .../tests/revert-and-restart-en.test.ts | 13 +- core/tests/revert-test/tests/utils.ts | 2 +- docker/contract-verifier/Dockerfile | 4 +- docker/external-node/Dockerfile | 6 +- docker/server-v2/Dockerfile | 8 +- docker/snapshots-creator/Dockerfile | 4 +- docker/verified-sources-fetcher/Dockerfile | 4 +- flake.nix | 12 +- infrastructure/zk/src/server.ts | 8 +- .../crates/bin/prover_cli/src/config/mod.rs | 2 +- prover/crates/bin/prover_cli/src/helper.rs | 2 +- .../src/vk_commitment_helper.rs | 2 +- prover/crates/lib/keystore/src/keystore.rs | 2 +- .../crates/common/src/external_node.rs | 2 +- zkstack_cli/crates/common/src/server.rs | 4 +- .../src/commands/contract_verifier/build.rs | 2 +- .../src/commands/contract_verifier/run.rs | 2 +- .../zkstack/src/commands/dev/commands/fmt.rs | 2 +- .../zkstack/src/commands/dev/commands/lint.rs | 4 +- .../src/commands/dev/commands/snapshot.rs | 2 +- .../commands/dev/commands/test/loadtest.rs | 47 +-- .../src/commands/dev/commands/test/rust.rs | 2 +- .../src/commands/external_node/build.rs | 2 +- .../crates/zkstack/src/commands/server.rs | 2 +- 41 files changed, 452 insertions(+), 439 deletions(-) delete mode 100644 Cargo.toml rename Cargo.lock => core/Cargo.lock (100%) create mode 100644 core/Cargo.toml rename deny.toml => core/deny.toml (100%) diff --git a/.dockerignore b/.dockerignore index 39efdabca19a..9be9fd580858 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,8 +20,8 @@ keys/setup !prover/ !yarn.lock !package.json -!Cargo.lock -!Cargo.toml +!core/Cargo.lock +!core/Cargo.toml !contracts/ !setup_2\^26.key !setup_2\^24.key diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 1f0c6b945b65..df99db16605f 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -15,8 +15,6 @@ check_fmt () { fi } -check_fmt - -cd prover/ - -check_fmt +( cd core/ && check_fmt ) +( cd prover/ && check_fmt ) +( cd zkstack_cli/ && check_fmt ) diff --git a/.github/workflows/cargo-license.yaml b/.github/workflows/cargo-license.yaml index 72eb8d0d865b..8b0c095c628c 100644 --- a/.github/workflows/cargo-license.yaml +++ b/.github/workflows/cargo-license.yaml @@ -7,5 +7,6 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: EmbarkStudios/cargo-deny-action@8371184bd11e21dcf8ac82ebf8c9c9f74ebf7268 # v2.0.1 with: + manifest-path: "./core/Cargo.toml" command: check command-arguments: "--hide-inclusion-graph" diff --git a/.github/workflows/ci-core-reusable.yml b/.github/workflows/ci-core-reusable.yml index 25a0da838f42..f44b1f54dc02 100644 --- a/.github/workflows/ci-core-reusable.yml +++ b/.github/workflows/ci-core-reusable.yml @@ -79,7 +79,7 @@ jobs: ci_run zkstack dev test rust # Benchmarks are not tested by `cargo nextest` unless specified explicitly, and even then `criterion` harness is incompatible # with how `cargo nextest` runs tests. Thus, we run criterion-based benchmark tests manually. - ci_run cargo test --release -p vm-benchmark --bench oneshot --bench batch + ci_run cargo test --manifest-path ./core/Cargo.toml --release -p vm-benchmark --bench oneshot --bench batch loadtest: runs-on: [ matterlabs-ci-runner-high-performance ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 899eaea4b445..a0b7ee1bc40e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,8 +57,8 @@ jobs: - '.github/workflows/build-contract-verifier-template.yml' - '.github/workflows/ci-core-reusable.yml' - '.github/workflows/ci-core-lint-reusable.yml' - - 'Cargo.toml' - - 'Cargo.lock' + - './core/Cargo.toml' + - './core/Cargo.lock' - 'zkstack_cli/**' - '!**/*.md' - '!**/*.MD' diff --git a/.github/workflows/protobuf.yaml b/.github/workflows/protobuf.yaml index f0565919ded1..0d7f43f1c2a2 100644 --- a/.github/workflows/protobuf.yaml +++ b/.github/workflows/protobuf.yaml @@ -47,13 +47,13 @@ jobs: git checkout $(git merge-base $BASE $HEAD) --recurse-submodules working-directory: ./before - name: compile before - run: cargo check --all-targets - working-directory: ./before/ + run: cargo check --manifest-path ./core/Cargo.toml --all-targets + working-directory: ./before - name: build before.binpb run: > perl -ne 'print "$1\n" if /PROTOBUF_DESCRIPTOR="(.*)"/' - `find ./before/target/debug/build/*/output` - | xargs cat > ./before.binpb + `find ./before/core/target/debug/build/*/output` + | xargs cat > ./before/.binpb # after - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 @@ -62,12 +62,12 @@ jobs: path: after submodules: recursive - name: compile after - run: cargo check --all-targets + run: cargo check --manifest-path ./core/Cargo.toml --all-targets working-directory: ./after - name: build after.binpb run: > perl -ne 'print "$1\n" if /PROTOBUF_DESCRIPTOR="(.*)"/' - `find ./after/target/debug/build/*/output` + `find ./after/core/target/debug/build/*/output` | xargs cat > ./after.binpb # compare @@ -75,6 +75,8 @@ jobs: with: github_token: ${{ github.token }} - name: buf breaking - run: > - buf breaking './after.binpb' --against './before.binpb' --exclude-path 'zksync/config/experimental.proto' - --config '{"version":"v1","breaking":{"use":["WIRE_JSON","WIRE"]}}' --error-format 'github-actions' + run: | + pwd + ls -la + buf breaking './after.binpb' --against './before.binpb' --exclude-path 'zksync/config/experimental.proto' \ + --config '{"version":"v1","breaking":{"use":["WIRE_JSON","WIRE"]}}' --error-format 'github-actions' diff --git a/.github/workflows/vm-perf-comparison.yml b/.github/workflows/vm-perf-comparison.yml index ac83485a2c12..59e44023243c 100644 --- a/.github/workflows/vm-perf-comparison.yml +++ b/.github/workflows/vm-perf-comparison.yml @@ -53,8 +53,10 @@ jobs: run: | ci_run zkstackup -g --local ci_run zkstack dev contracts - ci_run cargo bench --package vm-benchmark --bench instructions -- --verbose || echo "Instructions benchmark is missing" - ci_run cargo run --package vm-benchmark --release --bin instruction_counts | tee base-opcodes + ci_run cargo bench --manifest-path ./core/Cargo.toml \ + --package vm-benchmark --bench instructions -- --verbose || echo "Instructions benchmark is missing" + ci_run cargo run --manifest-path ./core/Cargo.toml \ + --package vm-benchmark --release --bin instruction_counts | tee base-opcodes - name: checkout PR run: | @@ -69,7 +71,8 @@ jobs: ci_run zkstack dev contracts ci_run cargo bench --package vm-benchmark --bench instructions -- --verbose - ci_run cargo bench --package vm-benchmark --bench instructions -- --print > instructions.log 2>/dev/null + ci_run cargo bench --manifest-path ./core/Cargo.toml \ + --package vm-benchmark --bench instructions -- --print > instructions.log 2>/dev/null # Output all lines from the benchmark result starting from the "## ..." comparison header. # Since the output spans multiple lines, we use a heredoc declaration. EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) @@ -77,7 +80,8 @@ jobs: sed -n '/^## /,$p' instructions.log >> $GITHUB_OUTPUT echo "$EOF" >> $GITHUB_OUTPUT - ci_run cargo run --package vm-benchmark --release --bin instruction_counts -- --diff base-opcodes > opcodes.log + ci_run cargo run --manifest-path ./core/Cargo.toml \ + --package vm-benchmark --release --bin instruction_counts -- --diff base-opcodes > opcodes.log echo "opcodes<<$EOF" >> $GITHUB_OUTPUT sed -n '/^## /,$p' opcodes.log >> $GITHUB_OUTPUT echo "$EOF" >> $GITHUB_OUTPUT diff --git a/.gitignore b/.gitignore index 471a601cc34b..d92880f5ae48 100644 --- a/.gitignore +++ b/.gitignore @@ -26,12 +26,6 @@ zksync_pk.key dist todo -Cargo.lock -!/Cargo.lock -!/infrastructure/zksync-crypto/Cargo.lock -!/prover/Cargo.lock -!/zkstack_cli/Cargo.lock - /etc/env/target/* /etc/env/.current /etc/env/configs/* diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 21a1a342f1ab..000000000000 --- a/Cargo.toml +++ /dev/null @@ -1,326 +0,0 @@ -[workspace] -members = [ - # Binaries - "core/bin/block_reverter", - "core/bin/contract-verifier", - "core/bin/custom_genesis_export", - "core/bin/external_node", - "core/bin/merkle_tree_consistency_checker", - "core/bin/snapshots_creator", - "core/bin/selector_generator", - "core/bin/system-constants-generator", - "core/bin/verified_sources_fetcher", - "core/bin/zksync_server", - "core/bin/genesis_generator", - "core/bin/zksync_tee_prover", - # Node services - "core/node/node_framework", - "core/node/proof_data_handler", - "core/node/block_reverter", - "core/node/commitment_generator", - "core/node/house_keeper", - "core/node/genesis", - "core/node/shared_metrics", - "core/node/db_pruner", - "core/node/fee_model", - "core/node/da_dispatcher", - "core/node/eth_sender", - "core/node/vm_runner", - "core/node/test_utils", - "core/node/state_keeper", - "core/node/reorg_detector", - "core/node/consistency_checker", - "core/node/metadata_calculator", - "core/node/node_sync", - "core/node/node_storage_init", - "core/node/consensus", - "core/node/contract_verification_server", - "core/node/api_server", - "core/node/base_token_adjuster", - "core/node/external_proof_integration_api", - "core/node/logs_bloom_backfill", - "core/node/da_clients", - # Libraries - "core/lib/db_connection", - "core/lib/zksync_core_leftovers", - "core/lib/basic_types", - "core/lib/config", - "core/lib/constants", - "core/lib/contract_verifier", - "core/lib/contracts", - "core/lib/circuit_breaker", - "core/lib/dal", - "core/lib/env_config", - "core/lib/da_client", - "core/lib/eth_client", - "core/lib/eth_signer", - "core/lib/l1_contract_interface", - "core/lib/mempool", - "core/lib/merkle_tree", - "core/lib/mini_merkle_tree", - "core/lib/node_framework_derive", - "core/lib/object_store", - "core/lib/prover_interface", - "core/lib/queued_job_processor", - "core/lib/state", - "core/lib/storage", - "core/lib/tee_verifier", - "core/lib/types", - "core/lib/protobuf_config", - "core/lib/utils", - "core/lib/vlog", - "core/lib/multivm", - "core/lib/vm_interface", - "core/lib/vm_executor", - "core/lib/web3_decl", - "core/lib/snapshots_applier", - "core/lib/crypto_primitives", - "core/lib/external_price_api", - "core/lib/test_contracts", - # Test infrastructure - "core/tests/loadnext", - "core/tests/vm-benchmark", -] -resolver = "2" - -exclude = [] - -# for `perf` profiling -[profile.perf] -inherits = "release" -debug = true - -[workspace.package] -version = "0.1.0" -edition = "2021" -authors = ["The Matter Labs Team "] -homepage = "https://zksync.io/" -repository = "https://github.com/matter-labs/zksync-era" -license = "MIT OR Apache-2.0" -keywords = ["blockchain", "zksync"] -categories = ["cryptography"] - -[workspace.dependencies] -# "External" dependencies -anyhow = "1" -assert_matches = "1.5" -async-trait = "0.1" -async-recursion = "1" -axum = "0.7.5" -backon = "0.4.4" -bigdecimal = "0.4.5" -bincode = "1" -blake2 = "0.10" -bytes = "1" -chrono = "0.4" -clap = "4.2.2" -codegen = "0.2.0" -const-decoder = "0.4.0" -criterion = "0.4.0" -ctrlc = "3.1" -dashmap = "5.5.3" -derive_more = "1.0.0" -envy = "0.4" -ethabi = "18.0.0" -flate2 = "1.0.28" -fraction = "0.15.3" -futures = "0.3" -futures-util = "0.3" -glob = "0.3" -google-cloud-auth = "0.16.0" -google-cloud-storage = "0.20.0" -governor = "0.4.2" -hex = "0.4" -http = "1.1" -http-body-util = "0.1.2" -httpmock = "0.7.0" -hyper = "1.3" -insta = "1.29.0" -itertools = "0.10" -jsonrpsee = { version = "0.23", default-features = false } -leb128 = "0.2.5" -lru = { version = "0.12.1", default-features = false } -mini-moka = "0.10.0" -num = "0.4.0" -num_cpus = "1.13" -num_enum = "0.7.2" -octocrab = "0.41" -once_cell = "1" -opentelemetry = "0.24.0" -opentelemetry_sdk = "0.24.0" -opentelemetry-otlp = "0.17.0" -opentelemetry-semantic-conventions = "0.16.0" -opentelemetry-appender-tracing = "0.5" -pin-project-lite = "0.2.13" -pretty_assertions = "1" -prost = "0.12.6" -rand = "0.8" -rayon = "1.3.1" -regex = "1" -reqwest = "0.12" -rlp = "0.5" -rocksdb = "0.21" -rustc_version = "0.4.0" -rustls = "0.23" -secp256k1 = { version = "0.27.0", features = ["recovery", "global-context"] } -secrecy = "0.8.0" -semver = "1" -sentry = "0.31" -serde = "1" -serde_json = "1" -serde_with = "1" -serde_yaml = "0.9" -sha2 = "0.10.8" -sha3 = "0.10.8" -sqlx = "0.8.1" -static_assertions = "1.1" -structopt = "0.3.20" -strum = "0.26" -tempfile = "3.0.2" -test-casing = "0.1.2" -test-log = "0.2.15" -thiserror = "1" -thread_local = "1.1" -tikv-jemallocator = "0.5" -tiny-keccak = "2" -tokio = "1" -tower = "0.4.13" -tower-http = "0.5.2" -tracing = "0.1" -tracing-subscriber = "0.3" -tracing-opentelemetry = "0.25.0" -time = "0.3.36" # Has to be same as used by `tracing-subscriber` -url = "2" -web3 = "0.19.0" -yab = "0.1.0" - -# Proc-macro -syn = "2.0" -quote = "1.0" -proc-macro2 = "1.0" -trybuild = "1.0" - -# "Internal" dependencies -vise = "0.2.0" -vise-exporter = "0.2.0" -foundry-compilers = { version = "0.11.6", git = "https://github.com/Moonsong-Labs/compilers.git", rev = "7c69695e5c75451f158dd2456bf8c94a7492ea0b" } - -# DA clients' dependencies -# Avail -base58 = "0.2.0" -scale-encode = "0.5.0" -blake2b_simd = "1.0.2" -subxt-metadata = "0.34.0" -parity-scale-codec = { version = "3.6.9", default-features = false } -subxt-signer = { version = "0.34", default-features = false } - -# Celestia -celestia-types = "0.6.1" -bech32 = "0.11.0" -ripemd = "0.1.3" -tonic = { version = "0.11.0", default-features = false } -pbjson-types = "0.6.0" - -# Eigen -tokio-stream = "0.1.16" - -# Here and below: -# We *always* pin the latest version of protocol to disallow accidental changes in the execution logic. -# However, for the historical version of protocol crates, we have lax requirements. Otherwise, -# Bumping a crypto dependency like `boojum` would require us to republish all the historical packages. -circuit_encodings = "=0.150.19" -circuit_sequencer_api = "=0.150.19" -circuit_definitions = "=0.150.19" -crypto_codegen = { package = "zksync_solidity_vk_codegen",version = "=0.30.12" } -kzg = { package = "zksync_kzg", version = "=0.150.19" } -zk_evm = { version = "=0.133.0" } -zk_evm_1_3_1 = { package = "zk_evm", version = "0.131.0-rc.2" } -zk_evm_1_3_3 = { package = "zk_evm", version = "0.133" } -zk_evm_1_4_0 = { package = "zk_evm", version = "0.140" } -zk_evm_1_4_1 = { package = "zk_evm", version = "0.141" } -zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.19" } -fflonk = "=0.30.12" - -# New VM; pinned to a specific commit because of instability -zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "457d8a7eea9093af9440662e33e598c13ba41633" } - -# Consensus dependencies. -zksync_concurrency = "=0.7.0" -zksync_consensus_bft = "=0.7.0" -zksync_consensus_crypto = "=0.7.0" -zksync_consensus_executor = "=0.7.0" -zksync_consensus_network = "=0.7.0" -zksync_consensus_roles = "=0.7.0" -zksync_consensus_storage = "=0.7.0" -zksync_consensus_utils = "=0.7.0" -zksync_protobuf = "=0.7.0" -zksync_protobuf_build = "=0.7.0" - -# "Local" dependencies -zksync_multivm = { version = "0.1.0", path = "core/lib/multivm" } -zksync_vlog = { version = "0.1.0", path = "core/lib/vlog" } -zksync_vm_interface = { version = "0.1.0", path = "core/lib/vm_interface" } -zksync_vm_executor = { version = "0.1.0", path = "core/lib/vm_executor" } -zksync_basic_types = { version = "0.1.0", path = "core/lib/basic_types" } -zksync_circuit_breaker = { version = "0.1.0", path = "core/lib/circuit_breaker" } -zksync_config = { version = "0.1.0", path = "core/lib/config" } -zksync_contract_verifier_lib = { version = "0.1.0", path = "core/lib/contract_verifier" } -zksync_contracts = { version = "0.1.0", path = "core/lib/contracts" } -zksync_core_leftovers = { version = "0.1.0", path = "core/lib/zksync_core_leftovers" } -zksync_dal = { version = "0.1.0", path = "core/lib/dal" } -zksync_db_connection = { version = "0.1.0", path = "core/lib/db_connection" } -zksync_env_config = { version = "0.1.0", path = "core/lib/env_config" } -zksync_eth_client = { version = "0.1.0", path = "core/lib/eth_client" } -zksync_da_client = { version = "0.1.0", path = "core/lib/da_client" } -zksync_eth_signer = { version = "0.1.0", path = "core/lib/eth_signer" } -zksync_health_check = { version = "0.1.0", path = "core/lib/health_check" } -zksync_l1_contract_interface = { version = "0.1.0", path = "core/lib/l1_contract_interface" } -zksync_mempool = { version = "0.1.0", path = "core/lib/mempool" } -zksync_merkle_tree = { version = "0.1.0", path = "core/lib/merkle_tree" } -zksync_bin_metadata = { version = "0.1.0", path = "core/lib/bin_metadata" } -zksync_mini_merkle_tree = { version = "0.1.0", path = "core/lib/mini_merkle_tree" } -zksync_object_store = { version = "0.1.0", path = "core/lib/object_store" } -zksync_protobuf_config = { version = "0.1.0", path = "core/lib/protobuf_config" } -zksync_prover_interface = { version = "0.1.0", path = "core/lib/prover_interface" } -zksync_queued_job_processor = { version = "0.1.0", path = "core/lib/queued_job_processor" } -zksync_snapshots_applier = { version = "0.1.0", path = "core/lib/snapshots_applier" } -zksync_state = { version = "0.1.0", path = "core/lib/state" } -zksync_storage = { version = "0.1.0", path = "core/lib/storage" } -zksync_system_constants = { version = "0.1.0", path = "core/lib/constants" } -zksync_tee_verifier = { version = "0.1.0", path = "core/lib/tee_verifier" } -zksync_test_contracts = { version = "0.1.0", path = "core/lib/test_contracts" } -zksync_types = { version = "0.1.0", path = "core/lib/types" } -zksync_utils = { version = "0.1.0", path = "core/lib/utils" } -zksync_web3_decl = { version = "0.1.0", path = "core/lib/web3_decl" } -zksync_crypto_primitives = { version = "0.1.0", path = "core/lib/crypto_primitives" } -zksync_external_price_api = { version = "0.1.0", path = "core/lib/external_price_api" } - -# Framework and components -zksync_node_framework = { version = "0.1.0", path = "core/node/node_framework" } -zksync_node_framework_derive = { version = "0.1.0", path = "core/lib/node_framework_derive" } -zksync_eth_watch = { version = "0.1.0", path = "core/node/eth_watch" } -zksync_shared_metrics = { version = "0.1.0", path = "core/node/shared_metrics" } -zksync_proof_data_handler = { version = "0.1.0", path = "core/node/proof_data_handler" } -zksync_block_reverter = { version = "0.1.0", path = "core/node/block_reverter" } -zksync_commitment_generator = { version = "0.1.0", path = "core/node/commitment_generator" } -zksync_house_keeper = { version = "0.1.0", path = "core/node/house_keeper" } -zksync_node_genesis = { version = "0.1.0", path = "core/node/genesis" } -zksync_da_dispatcher = { version = "0.1.0", path = "core/node/da_dispatcher" } -zksync_da_clients = { version = "0.1.0", path = "core/node/da_clients" } -zksync_eth_sender = { version = "0.1.0", path = "core/node/eth_sender" } -zksync_node_db_pruner = { version = "0.1.0", path = "core/node/db_pruner" } -zksync_node_fee_model = { version = "0.1.0", path = "core/node/fee_model" } -zksync_vm_runner = { version = "0.1.0", path = "core/node/vm_runner" } -zksync_external_proof_integration_api = { version = "0.1.0", path = "core/node/external_proof_integration_api" } -zksync_node_test_utils = { version = "0.1.0", path = "core/node/test_utils" } -zksync_state_keeper = { version = "0.1.0", path = "core/node/state_keeper" } -zksync_reorg_detector = { version = "0.1.0", path = "core/node/reorg_detector" } -zksync_consistency_checker = { version = "0.1.0", path = "core/node/consistency_checker" } -zksync_metadata_calculator = { version = "0.1.0", path = "core/node/metadata_calculator" } -zksync_node_sync = { version = "0.1.0", path = "core/node/node_sync" } -zksync_node_storage_init = { version = "0.1.0", path = "core/node/node_storage_init" } -zksync_node_consensus = { version = "0.1.0", path = "core/node/consensus" } -zksync_contract_verification_server = { version = "0.1.0", path = "core/node/contract_verification_server" } -zksync_node_api_server = { version = "0.1.0", path = "core/node/api_server" } -zksync_base_token_adjuster = { version = "0.1.0", path = "core/node/base_token_adjuster" } -zksync_logs_bloom_backfill = { version = "0.1.0", path = "core/node/logs_bloom_backfill" } diff --git a/Cargo.lock b/core/Cargo.lock similarity index 100% rename from Cargo.lock rename to core/Cargo.lock diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 000000000000..1c071c2839fa --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,326 @@ +[workspace] +members = [ + # Binaries + "bin/block_reverter", + "bin/contract-verifier", + "bin/custom_genesis_export", + "bin/external_node", + "bin/merkle_tree_consistency_checker", + "bin/snapshots_creator", + "bin/selector_generator", + "bin/system-constants-generator", + "bin/verified_sources_fetcher", + "bin/zksync_server", + "bin/genesis_generator", + "bin/zksync_tee_prover", + # Node services + "node/node_framework", + "node/proof_data_handler", + "node/block_reverter", + "node/commitment_generator", + "node/house_keeper", + "node/genesis", + "node/shared_metrics", + "node/db_pruner", + "node/fee_model", + "node/da_dispatcher", + "node/eth_sender", + "node/vm_runner", + "node/test_utils", + "node/state_keeper", + "node/reorg_detector", + "node/consistency_checker", + "node/metadata_calculator", + "node/node_sync", + "node/node_storage_init", + "node/consensus", + "node/contract_verification_server", + "node/api_server", + "node/base_token_adjuster", + "node/external_proof_integration_api", + "node/logs_bloom_backfill", + "node/da_clients", + # Libraries + "lib/db_connection", + "lib/zksync_core_leftovers", + "lib/basic_types", + "lib/config", + "lib/constants", + "lib/contract_verifier", + "lib/contracts", + "lib/circuit_breaker", + "lib/dal", + "lib/env_config", + "lib/da_client", + "lib/eth_client", + "lib/eth_signer", + "lib/l1_contract_interface", + "lib/mempool", + "lib/merkle_tree", + "lib/mini_merkle_tree", + "lib/node_framework_derive", + "lib/object_store", + "lib/prover_interface", + "lib/queued_job_processor", + "lib/state", + "lib/storage", + "lib/tee_verifier", + "lib/types", + "lib/protobuf_config", + "lib/utils", + "lib/vlog", + "lib/multivm", + "lib/vm_interface", + "lib/vm_executor", + "lib/web3_decl", + "lib/snapshots_applier", + "lib/crypto_primitives", + "lib/external_price_api", + "lib/test_contracts", + # Test infrastructure + "tests/loadnext", + "tests/vm-benchmark", +] +resolver = "2" + +exclude = [] + +# for `perf` profiling +[profile.perf] +inherits = "release" +debug = true + +[workspace.package] +version = "0.1.0" +edition = "2021" +authors = ["The Matter Labs Team "] +homepage = "https://zksync.io/" +repository = "https://github.com/matter-labs/zksync-era" +license = "MIT OR Apache-2.0" +keywords = ["blockchain", "zksync"] +categories = ["cryptography"] + +[workspace.dependencies] +# "External" dependencies +anyhow = "1" +assert_matches = "1.5" +async-trait = "0.1" +async-recursion = "1" +axum = "0.7.5" +backon = "0.4.4" +bigdecimal = "0.4.5" +bincode = "1" +blake2 = "0.10" +bytes = "1" +chrono = "0.4" +clap = "4.2.2" +codegen = "0.2.0" +const-decoder = "0.4.0" +criterion = "0.4.0" +ctrlc = "3.1" +dashmap = "5.5.3" +derive_more = "1.0.0" +envy = "0.4" +ethabi = "18.0.0" +flate2 = "1.0.28" +fraction = "0.15.3" +futures = "0.3" +futures-util = "0.3" +glob = "0.3" +google-cloud-auth = "0.16.0" +google-cloud-storage = "0.20.0" +governor = "0.4.2" +hex = "0.4" +http = "1.1" +http-body-util = "0.1.2" +httpmock = "0.7.0" +hyper = "1.3" +insta = "1.29.0" +itertools = "0.10" +jsonrpsee = { version = "0.23", default-features = false } +leb128 = "0.2.5" +lru = { version = "0.12.1", default-features = false } +mini-moka = "0.10.0" +num = "0.4.0" +num_cpus = "1.13" +num_enum = "0.7.2" +octocrab = "0.41" +once_cell = "1" +opentelemetry = "0.24.0" +opentelemetry_sdk = "0.24.0" +opentelemetry-otlp = "0.17.0" +opentelemetry-semantic-conventions = "0.16.0" +opentelemetry-appender-tracing = "0.5" +pin-project-lite = "0.2.13" +pretty_assertions = "1" +prost = "0.12.6" +rand = "0.8" +rayon = "1.3.1" +regex = "1" +reqwest = "0.12" +rlp = "0.5" +rocksdb = "0.21" +rustc_version = "0.4.0" +rustls = "0.23" +secp256k1 = { version = "0.27.0", features = ["recovery", "global-context"] } +secrecy = "0.8.0" +semver = "1" +sentry = "0.31" +serde = "1" +serde_json = "1" +serde_with = "1" +serde_yaml = "0.9" +sha2 = "0.10.8" +sha3 = "0.10.8" +sqlx = "0.8.1" +static_assertions = "1.1" +structopt = "0.3.20" +strum = "0.26" +tempfile = "3.0.2" +test-casing = "0.1.2" +test-log = "0.2.15" +thiserror = "1" +thread_local = "1.1" +tikv-jemallocator = "0.5" +tiny-keccak = "2" +tokio = "1" +tower = "0.4.13" +tower-http = "0.5.2" +tracing = "0.1" +tracing-subscriber = "0.3" +tracing-opentelemetry = "0.25.0" +time = "0.3.36" # Has to be same as used by `tracing-subscriber` +url = "2" +web3 = "0.19.0" +yab = "0.1.0" + +# Proc-macro +syn = "2.0" +quote = "1.0" +proc-macro2 = "1.0" +trybuild = "1.0" + +# "Internal" dependencies +vise = "0.2.0" +vise-exporter = "0.2.0" +foundry-compilers = { version = "0.11.6", git = "https://github.com/Moonsong-Labs/compilers.git", rev = "7c69695e5c75451f158dd2456bf8c94a7492ea0b" } + +# DA clients' dependencies +# Avail +base58 = "0.2.0" +scale-encode = "0.5.0" +blake2b_simd = "1.0.2" +subxt-metadata = "0.34.0" +parity-scale-codec = { version = "3.6.9", default-features = false } +subxt-signer = { version = "0.34", default-features = false } + +# Celestia +celestia-types = "0.6.1" +bech32 = "0.11.0" +ripemd = "0.1.3" +tonic = { version = "0.11.0", default-features = false } +pbjson-types = "0.6.0" + +# Eigen +tokio-stream = "0.1.16" + +# Here and below: +# We *always* pin the latest version of protocol to disallow accidental changes in the execution logic. +# However, for the historical version of protocol crates, we have lax requirements. Otherwise, +# Bumping a crypto dependency like `boojum` would require us to republish all the historical packages. +circuit_encodings = "=0.150.19" +circuit_sequencer_api = "=0.150.19" +circuit_definitions = "=0.150.19" +crypto_codegen = { package = "zksync_solidity_vk_codegen",version = "=0.30.12" } +kzg = { package = "zksync_kzg", version = "=0.150.19" } +zk_evm = { version = "=0.133.0" } +zk_evm_1_3_1 = { package = "zk_evm", version = "0.131.0-rc.2" } +zk_evm_1_3_3 = { package = "zk_evm", version = "0.133" } +zk_evm_1_4_0 = { package = "zk_evm", version = "0.140" } +zk_evm_1_4_1 = { package = "zk_evm", version = "0.141" } +zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.19" } +fflonk = "=0.30.12" + +# New VM; pinned to a specific commit because of instability +zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "457d8a7eea9093af9440662e33e598c13ba41633" } + +# Consensus dependencies. +zksync_concurrency = "=0.7.0" +zksync_consensus_bft = "=0.7.0" +zksync_consensus_crypto = "=0.7.0" +zksync_consensus_executor = "=0.7.0" +zksync_consensus_network = "=0.7.0" +zksync_consensus_roles = "=0.7.0" +zksync_consensus_storage = "=0.7.0" +zksync_consensus_utils = "=0.7.0" +zksync_protobuf = "=0.7.0" +zksync_protobuf_build = "=0.7.0" + +# "Local" dependencies +zksync_multivm = { version = "0.1.0", path = "lib/multivm" } +zksync_vlog = { version = "0.1.0", path = "lib/vlog" } +zksync_vm_interface = { version = "0.1.0", path = "lib/vm_interface" } +zksync_vm_executor = { version = "0.1.0", path = "lib/vm_executor" } +zksync_basic_types = { version = "0.1.0", path = "lib/basic_types" } +zksync_circuit_breaker = { version = "0.1.0", path = "lib/circuit_breaker" } +zksync_config = { version = "0.1.0", path = "lib/config" } +zksync_contract_verifier_lib = { version = "0.1.0", path = "lib/contract_verifier" } +zksync_contracts = { version = "0.1.0", path = "lib/contracts" } +zksync_core_leftovers = { version = "0.1.0", path = "lib/zksync_core_leftovers" } +zksync_dal = { version = "0.1.0", path = "lib/dal" } +zksync_db_connection = { version = "0.1.0", path = "lib/db_connection" } +zksync_env_config = { version = "0.1.0", path = "lib/env_config" } +zksync_eth_client = { version = "0.1.0", path = "lib/eth_client" } +zksync_da_client = { version = "0.1.0", path = "lib/da_client" } +zksync_eth_signer = { version = "0.1.0", path = "lib/eth_signer" } +zksync_health_check = { version = "0.1.0", path = "lib/health_check" } +zksync_l1_contract_interface = { version = "0.1.0", path = "lib/l1_contract_interface" } +zksync_mempool = { version = "0.1.0", path = "lib/mempool" } +zksync_merkle_tree = { version = "0.1.0", path = "lib/merkle_tree" } +zksync_bin_metadata = { version = "0.1.0", path = "lib/bin_metadata" } +zksync_mini_merkle_tree = { version = "0.1.0", path = "lib/mini_merkle_tree" } +zksync_object_store = { version = "0.1.0", path = "lib/object_store" } +zksync_protobuf_config = { version = "0.1.0", path = "lib/protobuf_config" } +zksync_prover_interface = { version = "0.1.0", path = "lib/prover_interface" } +zksync_queued_job_processor = { version = "0.1.0", path = "lib/queued_job_processor" } +zksync_snapshots_applier = { version = "0.1.0", path = "lib/snapshots_applier" } +zksync_state = { version = "0.1.0", path = "lib/state" } +zksync_storage = { version = "0.1.0", path = "lib/storage" } +zksync_system_constants = { version = "0.1.0", path = "lib/constants" } +zksync_tee_verifier = { version = "0.1.0", path = "lib/tee_verifier" } +zksync_test_contracts = { version = "0.1.0", path = "lib/test_contracts" } +zksync_types = { version = "0.1.0", path = "lib/types" } +zksync_utils = { version = "0.1.0", path = "lib/utils" } +zksync_web3_decl = { version = "0.1.0", path = "lib/web3_decl" } +zksync_crypto_primitives = { version = "0.1.0", path = "lib/crypto_primitives" } +zksync_external_price_api = { version = "0.1.0", path = "lib/external_price_api" } + +# Framework and components +zksync_node_framework = { version = "0.1.0", path = "node/node_framework" } +zksync_node_framework_derive = { version = "0.1.0", path = "lib/node_framework_derive" } +zksync_eth_watch = { version = "0.1.0", path = "node/eth_watch" } +zksync_shared_metrics = { version = "0.1.0", path = "node/shared_metrics" } +zksync_proof_data_handler = { version = "0.1.0", path = "node/proof_data_handler" } +zksync_block_reverter = { version = "0.1.0", path = "node/block_reverter" } +zksync_commitment_generator = { version = "0.1.0", path = "node/commitment_generator" } +zksync_house_keeper = { version = "0.1.0", path = "node/house_keeper" } +zksync_node_genesis = { version = "0.1.0", path = "node/genesis" } +zksync_da_dispatcher = { version = "0.1.0", path = "node/da_dispatcher" } +zksync_da_clients = { version = "0.1.0", path = "node/da_clients" } +zksync_eth_sender = { version = "0.1.0", path = "node/eth_sender" } +zksync_node_db_pruner = { version = "0.1.0", path = "node/db_pruner" } +zksync_node_fee_model = { version = "0.1.0", path = "node/fee_model" } +zksync_vm_runner = { version = "0.1.0", path = "node/vm_runner" } +zksync_external_proof_integration_api = { version = "0.1.0", path = "node/external_proof_integration_api" } +zksync_node_test_utils = { version = "0.1.0", path = "node/test_utils" } +zksync_state_keeper = { version = "0.1.0", path = "node/state_keeper" } +zksync_reorg_detector = { version = "0.1.0", path = "node/reorg_detector" } +zksync_consistency_checker = { version = "0.1.0", path = "node/consistency_checker" } +zksync_metadata_calculator = { version = "0.1.0", path = "node/metadata_calculator" } +zksync_node_sync = { version = "0.1.0", path = "node/node_sync" } +zksync_node_storage_init = { version = "0.1.0", path = "node/node_storage_init" } +zksync_node_consensus = { version = "0.1.0", path = "node/consensus" } +zksync_contract_verification_server = { version = "0.1.0", path = "node/contract_verification_server" } +zksync_node_api_server = { version = "0.1.0", path = "node/api_server" } +zksync_base_token_adjuster = { version = "0.1.0", path = "node/base_token_adjuster" } +zksync_logs_bloom_backfill = { version = "0.1.0", path = "node/logs_bloom_backfill" } diff --git a/core/bin/system-constants-generator/src/main.rs b/core/bin/system-constants-generator/src/main.rs index cd795f9f5326..69152545bdfe 100644 --- a/core/bin/system-constants-generator/src/main.rs +++ b/core/bin/system-constants-generator/src/main.rs @@ -212,7 +212,7 @@ fn generate_rust_fee_constants(intrinsic_gas_constants: &IntrinsicSystemGasConst } fn save_file(path_in_repo: &str, content: String) { - let zksync_home = Workspace::locate().core(); + let zksync_home = Workspace::locate().root(); let fee_constants_path = zksync_home.join(path_in_repo); fs::write(fee_constants_path, content) diff --git a/deny.toml b/core/deny.toml similarity index 100% rename from deny.toml rename to core/deny.toml diff --git a/core/lib/contract_verifier/src/resolver/env.rs b/core/lib/contract_verifier/src/resolver/env.rs index 798efde64348..75fdf4e7f472 100644 --- a/core/lib/contract_verifier/src/resolver/env.rs +++ b/core/lib/contract_verifier/src/resolver/env.rs @@ -23,7 +23,7 @@ pub(crate) struct EnvCompilerResolver { impl Default for EnvCompilerResolver { fn default() -> Self { Self { - home_dir: Workspace::locate().core(), + home_dir: Workspace::locate().root(), } } } diff --git a/core/lib/contracts/src/lib.rs b/core/lib/contracts/src/lib.rs index 87f102be39d6..e7cf1508f733 100644 --- a/core/lib/contracts/src/lib.rs +++ b/core/lib/contracts/src/lib.rs @@ -82,7 +82,7 @@ const _FAIL_ON_RECEIVE_CONTRACT_FILE: &str = "contracts/l1-contracts/artifacts/contracts/zksync/dev-contracts/FailOnReceive.sol/FailOnReceive.json"; fn home_path() -> PathBuf { - Workspace::locate().core() + Workspace::locate().root() } fn read_file_to_json_value(path: impl AsRef + std::fmt::Debug) -> Option { diff --git a/core/lib/utils/src/env.rs b/core/lib/utils/src/env.rs index 8f4aa1da9400..0da0ddbc9f70 100644 --- a/core/lib/utils/src/env.rs +++ b/core/lib/utils/src/env.rs @@ -11,11 +11,9 @@ static WORKSPACE: OnceCell> = OnceCell::new(); /// Represents Cargo workspaces available in the repository. #[derive(Debug, Clone, Copy)] pub enum Workspace<'a> { - /// Workspace was not found. - /// Assumes that the code is running in a binary. - /// Will use the current directory as a fallback. - None, /// Root folder. + Root, + /// `core` folder. Core(&'a Path), /// `prover` folder. Prover(&'a Path), @@ -42,21 +40,30 @@ impl Workspace<'static> { result.ok() }) .as_deref(); - path.map_or(Self::None, Self::from) + path.map_or(Self::Root, Self::from) } } impl<'a> Workspace<'a> { + const CORE_DIRECTORY_NAME: &'static str = "core"; const PROVER_DIRECTORY_NAME: &'static str = "prover"; const ZKSTACK_CLI_DIRECTORY_NAME: &'static str = "zkstack_cli"; - /// Returns the path of the core workspace. - /// For `Workspace::None`, considers the current directory to represent core workspace. + /// Returns the path of the repository root. + pub fn root(self) -> PathBuf { + match self { + Self::Root => PathBuf::from("."), + Self::Core(path) | Self::Prover(path) | Self::ZkStackCli(path) => { + path.parent().unwrap().into() + } + } + } + + /// Returns the path of the `core` workspace. pub fn core(self) -> PathBuf { match self { - Self::None => PathBuf::from("."), Self::Core(path) => path.into(), - Self::Prover(path) | Self::ZkStackCli(path) => path.parent().unwrap().into(), + _ => self.root().join(Self::CORE_DIRECTORY_NAME), } } @@ -64,7 +71,7 @@ impl<'a> Workspace<'a> { pub fn prover(self) -> PathBuf { match self { Self::Prover(path) => path.into(), - _ => self.core().join(Self::PROVER_DIRECTORY_NAME), + _ => self.root().join(Self::PROVER_DIRECTORY_NAME), } } @@ -72,7 +79,7 @@ impl<'a> Workspace<'a> { pub fn zkstack_cli(self) -> PathBuf { match self { Self::ZkStackCli(path) => path.into(), - _ => self.core().join(Self::ZKSTACK_CLI_DIRECTORY_NAME), + _ => self.root().join(Self::ZKSTACK_CLI_DIRECTORY_NAME), } } } @@ -83,8 +90,10 @@ impl<'a> From<&'a Path> for Workspace<'a> { Self::Prover(path) } else if path.ends_with(Self::ZKSTACK_CLI_DIRECTORY_NAME) { Self::ZkStackCli(path) - } else { + } else if path.ends_with(Self::CORE_DIRECTORY_NAME) { Self::Core(path) + } else { + Self::Root } } } @@ -150,7 +159,6 @@ mod tests { let _pwd_protector = PwdProtector::new(); // Core. - let workspace = Workspace::locate(); assert_matches!(workspace, Workspace::Core(_)); let core_path = workspace.core(); diff --git a/core/tests/loadnext/src/fs_utils.rs b/core/tests/loadnext/src/fs_utils.rs index 0e5107f40861..9f44b1ff4946 100644 --- a/core/tests/loadnext/src/fs_utils.rs +++ b/core/tests/loadnext/src/fs_utils.rs @@ -17,7 +17,7 @@ pub struct Token { } pub fn read_tokens(network: Network) -> anyhow::Result> { - let home = Workspace::locate().core(); + let home = Workspace::locate().root(); let path = home.join(format!("etc/tokens/{network}.json")); let file = File::open(path)?; let reader = BufReader::new(file); diff --git a/core/tests/revert-test/tests/revert-and-restart-en.test.ts b/core/tests/revert-test/tests/revert-and-restart-en.test.ts index ca7f1735b356..a27e3836461a 100644 --- a/core/tests/revert-test/tests/revert-and-restart-en.test.ts +++ b/core/tests/revert-test/tests/revert-and-restart-en.test.ts @@ -54,7 +54,18 @@ function compileBinaries() { console.log('compiling binaries'); run( 'cargo', - ['build', '--release', '--bin', 'zksync_external_node', '--bin', 'zksync_server', '--bin', 'block_reverter'], + [ + 'build', + '--manifest-path', + './core/Cargo.toml', + '--release', + '--bin', + 'zksync_external_node', + '--bin', + 'zksync_server', + '--bin', + 'block_reverter' + ], { cwd: process.env.ZKSYNC_HOME } ); } diff --git a/core/tests/revert-test/tests/utils.ts b/core/tests/revert-test/tests/utils.ts index 8290598a1feb..d2cf9df837ad 100644 --- a/core/tests/revert-test/tests/utils.ts +++ b/core/tests/revert-test/tests/utils.ts @@ -148,7 +148,7 @@ async function runBlockReverter( `; } - const cmd = `cd ${pathToHome} && RUST_LOG=off cargo run --bin block_reverter --release -- ${args.join( + const cmd = `cd ${pathToHome} && RUST_LOG=off cargo run --manifest-path ./core/Cargo.toml --bin block_reverter --release -- ${args.join( ' ' )} ${fileConfigFlags}`; diff --git a/docker/contract-verifier/Dockerfile b/docker/contract-verifier/Dockerfile index d5f3c53db99f..14cae24c0642 100644 --- a/docker/contract-verifier/Dockerfile +++ b/docker/contract-verifier/Dockerfile @@ -17,7 +17,7 @@ ENV RUSTC_WRAPPER=${RUSTC_WRAPPER} WORKDIR /usr/src/zksync COPY . . -RUN cargo build --release --bin zksync_contract_verifier +RUN cargo build --manifest-path ./core/Cargo.toml --release --bin zksync_contract_verifier FROM ghcr.io/matter-labs/zksync-runtime-base:latest @@ -96,7 +96,7 @@ RUN mkdir -p /etc/vyper-bin/0.4.0 \ && mv vyper0.4.0 /etc/vyper-bin/0.4.0/vyper \ && chmod +x /etc/vyper-bin/0.4.0/vyper -COPY --from=builder /usr/src/zksync/target/release/zksync_contract_verifier /usr/bin/ +COPY --from=builder /usr/src/zksync/core/target/release/zksync_contract_verifier /usr/bin/ COPY contracts/system-contracts/zkout/ /contracts/system-contracts/zkout/ # CMD tail -f /dev/null diff --git a/docker/external-node/Dockerfile b/docker/external-node/Dockerfile index 2effe1051b4a..3c968b896e43 100644 --- a/docker/external-node/Dockerfile +++ b/docker/external-node/Dockerfile @@ -15,12 +15,12 @@ ENV RUSTC_WRAPPER=${RUSTC_WRAPPER} WORKDIR /usr/src/zksync COPY . . -RUN cargo build --release --bin zksync_external_node --bin block_reverter +RUN cargo build --manifest-path ./core/Cargo.toml --release --bin zksync_external_node --bin block_reverter FROM ghcr.io/matter-labs/zksync-runtime-base:latest -COPY --from=builder /usr/src/zksync/target/release/zksync_external_node /usr/bin -COPY --from=builder /usr/src/zksync/target/release/block_reverter /usr/bin +COPY --from=builder /usr/src/zksync/core/target/release/zksync_external_node /usr/bin +COPY --from=builder /usr/src/zksync/core/target/release/block_reverter /usr/bin COPY --from=builder /usr/local/cargo/bin/sqlx /usr/bin COPY --from=builder /usr/src/zksync/docker/external-node/entrypoint.sh /usr/bin COPY contracts/system-contracts/zkout/ /contracts/system-contracts/zkout/ diff --git a/docker/server-v2/Dockerfile b/docker/server-v2/Dockerfile index 9557156fa7c4..5855bab8d7ab 100644 --- a/docker/server-v2/Dockerfile +++ b/docker/server-v2/Dockerfile @@ -17,7 +17,7 @@ WORKDIR /usr/src/zksync COPY . . -RUN cargo build --release --features=rocksdb/io-uring --bin zksync_server --bin block_reverter --bin merkle_tree_consistency_checker +RUN cargo build --manifest-path ./core/Cargo.toml --release --features=rocksdb/io-uring --bin zksync_server --bin block_reverter --bin merkle_tree_consistency_checker FROM ghcr.io/matter-labs/zksync-runtime-base:latest @@ -28,9 +28,9 @@ EXPOSE 3000 EXPOSE 3031 EXPOSE 3030 -COPY --from=builder /usr/src/zksync/target/release/zksync_server /usr/bin -COPY --from=builder /usr/src/zksync/target/release/block_reverter /usr/bin -COPY --from=builder /usr/src/zksync/target/release/merkle_tree_consistency_checker /usr/bin +COPY --from=builder /usr/src/zksync/core/target/release/zksync_server /usr/bin +COPY --from=builder /usr/src/zksync/core/target/release/block_reverter /usr/bin +COPY --from=builder /usr/src/zksync/core/target/release/merkle_tree_consistency_checker /usr/bin COPY contracts/system-contracts/zkout/ /contracts/system-contracts/zkout/ COPY contracts/l1-contracts/out/ /contracts/l1-contracts/out/ COPY contracts/l2-contracts/zkout/ /contracts/l2-contracts/zkout/ diff --git a/docker/snapshots-creator/Dockerfile b/docker/snapshots-creator/Dockerfile index 2d3c83064981..753020094df7 100644 --- a/docker/snapshots-creator/Dockerfile +++ b/docker/snapshots-creator/Dockerfile @@ -15,13 +15,13 @@ ENV RUSTC_WRAPPER=${RUSTC_WRAPPER} WORKDIR /usr/src/zksync COPY . . -RUN cargo build --release --bin snapshots_creator +RUN cargo build --manifest-path ./core/Cargo.toml --release --bin snapshots_creator FROM ghcr.io/matter-labs/zksync-runtime-base:latest RUN apt-get update && apt-get install -y liburing-dev && \ rm -rf /var/lib/apt/lists/* -COPY --from=builder /usr/src/zksync/target/release/snapshots_creator /usr/bin +COPY --from=builder /usr/src/zksync/core/target/release/snapshots_creator /usr/bin ENTRYPOINT ["snapshots_creator"] diff --git a/docker/verified-sources-fetcher/Dockerfile b/docker/verified-sources-fetcher/Dockerfile index 87475f3187f3..ebc6619582fd 100644 --- a/docker/verified-sources-fetcher/Dockerfile +++ b/docker/verified-sources-fetcher/Dockerfile @@ -16,7 +16,7 @@ ENV RUSTC_WRAPPER=${RUSTC_WRAPPER} WORKDIR /usr/src/zksync COPY . . -RUN cargo build --release --bin verified_sources_fetcher +RUN cargo build --manifest-path ./core/Cargo.toml --release --bin verified_sources_fetcher FROM ghcr.io/matter-labs/zksync-runtime-base:latest @@ -26,6 +26,6 @@ RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor - RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list RUN apt-get update && apt-get install -y google-cloud-cli && rm -rf /var/lib/apt/lists/* -COPY --from=builder /usr/src/zksync/target/release/verified_sources_fetcher /usr/bin/ +COPY --from=builder /usr/src/zksync/core/target/release/verified_sources_fetcher /usr/bin/ ENTRYPOINT ["verified_sources_fetcher"] diff --git a/flake.nix b/flake.nix index 8c08e880910d..630d719aa4df 100644 --- a/flake.nix +++ b/flake.nix @@ -84,17 +84,7 @@ snappy.dev ]; - src = with pkgs.lib.fileset; toSource { - root = ./.; - fileset = unions [ - ./Cargo.lock - ./Cargo.toml - ./core - ./prover - ./zkstack_cli - ./.github/release-please/manifest.json - ]; - }; + src = ./core/.; env = { OPENSSL_NO_VENDOR = "1"; diff --git a/infrastructure/zk/src/server.ts b/infrastructure/zk/src/server.ts index 8b10559361ae..14a27f6a48be 100644 --- a/infrastructure/zk/src/server.ts +++ b/infrastructure/zk/src/server.ts @@ -17,7 +17,7 @@ export async function server(rebuildTree: boolean, uring: boolean, components?: if (components) { options += ` --components=${components}`; } - await utils.spawn(`cargo run --bin zksync_server --release ${options}`); + await utils.spawn(`cargo run --manifest-path core/Cargo.toml--bin zksync_server --release ${options}`); } export async function externalNode(reinit: boolean = false, args: string[]) { @@ -37,7 +37,9 @@ export async function externalNode(reinit: boolean = false, args: string[]) { clean(path.dirname(process.env.EN_MERKLE_TREE_PATH!)); } - await utils.spawn(`cargo run --release --bin zksync_external_node -- ${args.join(' ')}`); + await utils.spawn( + `cargo run --manifest-path core/Cargo.toml --release --bin zksync_external_node -- ${args.join(' ')}` + ); } async function create_genesis(cmd: string) { @@ -61,7 +63,7 @@ async function create_genesis(cmd: string) { export async function genesisFromSources() { // Note that that all the chains have the same chainId at genesis. It will be changed // via an upgrade transaction during the registration of the chain. - await create_genesis('cargo run --bin zksync_server --release -- --genesis'); + await create_genesis('cargo run --manifest-path core/Cargo.toml --bin zksync_server --release -- --genesis'); } export async function genesisFromBinary() { diff --git a/prover/crates/bin/prover_cli/src/config/mod.rs b/prover/crates/bin/prover_cli/src/config/mod.rs index b3df2e7d2c56..88cf3a55f908 100644 --- a/prover/crates/bin/prover_cli/src/config/mod.rs +++ b/prover/crates/bin/prover_cli/src/config/mod.rs @@ -6,7 +6,7 @@ pub fn get_envfile() -> anyhow::Result { if let Ok(envfile) = std::env::var("PLI__CONFIG") { return Ok(envfile.into()); } - Ok(Workspace::locate().core().join("etc/pliconfig")) + Ok(Workspace::locate().root().join("etc/pliconfig")) } pub fn load_envfile(path: impl AsRef) -> anyhow::Result<()> { diff --git a/prover/crates/bin/prover_cli/src/helper.rs b/prover/crates/bin/prover_cli/src/helper.rs index 7fe0c990e4e0..b793ce5f2be1 100644 --- a/prover/crates/bin/prover_cli/src/helper.rs +++ b/prover/crates/bin/prover_cli/src/helper.rs @@ -24,7 +24,7 @@ fn read_file_to_json_value(path: &PathBuf) -> serde_json::Value { } fn load_contract_if_present(path: &str) -> Contract { - let path = Workspace::locate().core().join(path); + let path = Workspace::locate().root().join(path); path.exists() .then(|| { serde_json::from_value(read_file_to_json_value(&path)["abi"].take()).unwrap_or_else( diff --git a/prover/crates/bin/vk_setup_data_generator_server_fri/src/vk_commitment_helper.rs b/prover/crates/bin/vk_setup_data_generator_server_fri/src/vk_commitment_helper.rs index 2753799dc722..2f5bbf269267 100644 --- a/prover/crates/bin/vk_setup_data_generator_server_fri/src/vk_commitment_helper.rs +++ b/prover/crates/bin/vk_setup_data_generator_server_fri/src/vk_commitment_helper.rs @@ -24,6 +24,6 @@ pub fn read_contract_toml() -> anyhow::Result { pub fn get_contract_toml_path() -> PathBuf { Workspace::locate() - .core() + .root() .join("etc/env/base/contracts.toml") } diff --git a/prover/crates/lib/keystore/src/keystore.rs b/prover/crates/lib/keystore/src/keystore.rs index fb84436916e7..c4ffb32ce73c 100644 --- a/prover/crates/lib/keystore/src/keystore.rs +++ b/prover/crates/lib/keystore/src/keystore.rs @@ -78,7 +78,7 @@ impl Keystore { // - We're running from the core workspace. // - We're running the binary from the docker. let data_dir_path = match Workspace::locate() { - Workspace::None => { + Workspace::Root => { // We're running a binary, likely in a docker. // Keys can be in one of a few paths. // We want to be very conservative here, and checking diff --git a/zkstack_cli/crates/common/src/external_node.rs b/zkstack_cli/crates/common/src/external_node.rs index 8a5cbc3cd14c..7f9031bae4ff 100644 --- a/zkstack_cli/crates/common/src/external_node.rs +++ b/zkstack_cli/crates/common/src/external_node.rs @@ -17,7 +17,7 @@ pub fn run( let cmd = Cmd::new( cmd!( shell, - "cargo run --release --bin zksync_external_node -- + "cargo run --manifest-path ./core/Cargo.toml --release --bin zksync_external_node -- --config-path {config_path} --secrets-path {secrets_path} --external-node-config-path {en_config_path} diff --git a/zkstack_cli/crates/common/src/server.rs b/zkstack_cli/crates/common/src/server.rs index 7f8c2a90e589..0dd93bcd3324 100644 --- a/zkstack_cli/crates/common/src/server.rs +++ b/zkstack_cli/crates/common/src/server.rs @@ -70,7 +70,7 @@ impl Server { let mut cmd = Cmd::new( cmd!( shell, - "cargo run --release --bin zksync_server {uring...} -- + "cargo run --manifest-path ./core/Cargo.toml --release --bin zksync_server {uring...} -- --genesis-path {genesis_path} --wallets-path {wallets_path} --config-path {general_path} @@ -96,7 +96,7 @@ impl Server { /// Builds the server. pub fn build(&self, shell: &Shell) -> anyhow::Result<()> { - let _dir_guard = shell.push_dir(&self.code_path); + let _dir_guard = shell.push_dir(self.code_path.join("core")); Cmd::new(cmd!(shell, "cargo build --release --bin zksync_server")).run()?; Ok(()) } diff --git a/zkstack_cli/crates/zkstack/src/commands/contract_verifier/build.rs b/zkstack_cli/crates/zkstack/src/commands/contract_verifier/build.rs index 384e9a389265..2e4107383d0a 100644 --- a/zkstack_cli/crates/zkstack/src/commands/contract_verifier/build.rs +++ b/zkstack_cli/crates/zkstack/src/commands/contract_verifier/build.rs @@ -13,7 +13,7 @@ pub(crate) async fn build(shell: &Shell) -> anyhow::Result<()> { let chain = ecosystem .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let _dir_guard = shell.push_dir(&chain.link_to_code); + let _dir_guard = shell.push_dir(chain.link_to_code.join("core")); logger::info(MSG_BUILDING_CONTRACT_VERIFIER); diff --git a/zkstack_cli/crates/zkstack/src/commands/contract_verifier/run.rs b/zkstack_cli/crates/zkstack/src/commands/contract_verifier/run.rs index fd872aa67248..fe4221e83a46 100644 --- a/zkstack_cli/crates/zkstack/src/commands/contract_verifier/run.rs +++ b/zkstack_cli/crates/zkstack/src/commands/contract_verifier/run.rs @@ -22,7 +22,7 @@ pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { let mut cmd = Cmd::new(cmd!( shell, - "cargo run --release --bin zksync_contract_verifier -- --config-path={config_path} --secrets-path={secrets_path}" + "cargo run --manifest-path ./core/Cargo.toml --release --bin zksync_contract_verifier -- --config-path={config_path} --secrets-path={secrets_path}" )); cmd = cmd.with_force_run(); cmd.run().context(MSG_FAILED_TO_RUN_CONTRACT_VERIFIER_ERR) diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/fmt.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/fmt.rs index af6985b006d2..82594cccf484 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/fmt.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/fmt.rs @@ -42,7 +42,7 @@ async fn prettier_contracts(shell: Shell, check: bool) -> anyhow::Result<()> { } async fn rustfmt(shell: Shell, check: bool, link_to_code: PathBuf) -> anyhow::Result<()> { - for dir in [".", "prover", "zkstack_cli"] { + for dir in ["core", "prover", "zkstack_cli"] { let spinner = Spinner::new(&msg_running_rustfmt_for_dir_spinner(dir)); let _dir = shell.push_dir(link_to_code.join(dir)); let mut cmd = cmd!(shell, "cargo fmt -- --config imports_granularity=Crate --config group_imports=StdExternalCrate"); diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/lint.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/lint.rs index 6b919b8cdb9d..7ba6fe27481a 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/lint.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/lint.rs @@ -70,12 +70,12 @@ pub fn run(shell: &Shell, args: LintArgs) -> anyhow::Result<()> { fn lint_rs(shell: &Shell, ecosystem: &EcosystemConfig, check: bool) -> anyhow::Result<()> { let spinner = Spinner::new(&msg_running_linter_for_extension_spinner(&Target::Rs)); - let link_to_code = &ecosystem.link_to_code; + let link_to_core = &ecosystem.link_to_code.join("core"); let lint_to_prover = &ecosystem.link_to_code.join("prover"); let link_to_zkstack = &ecosystem.link_to_code.join("zkstack_cli"); spinner.freeze(); - for path in [link_to_code, lint_to_prover, link_to_zkstack] { + for path in [link_to_core, lint_to_prover, link_to_zkstack] { let _dir_guard = shell.push_dir(path); let mut cmd = cmd!(shell, "cargo clippy"); let mut common_args = vec!["--locked", "--", "-D", "warnings"]; diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/snapshot.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/snapshot.rs index dcc41af228be..85aaec103c0b 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/snapshot.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/snapshot.rs @@ -32,7 +32,7 @@ async fn create(shell: &Shell) -> anyhow::Result<()> { logger::info(MSG_RUNNING_SNAPSHOT_CREATOR); - let mut cmd = Cmd::new(cmd!(shell, "cargo run --bin snapshots_creator --release -- --config-path={config_path} --secrets-path={secrets_path}")) + let mut cmd = Cmd::new(cmd!(shell, "cargo run --manifest-path ./core/Cargo.toml --bin snapshots_creator --release -- --config-path={config_path} --secrets-path={secrets_path}")) .env("RUST_LOG", "snapshots_creator=debug"); cmd = cmd.with_force_run(); diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs index 385335890c75..64ea474fa2fa 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs @@ -17,28 +17,31 @@ pub fn run(shell: &Shell) -> anyhow::Result<()> { .api_config .context("API config is not found")?; - let mut command = cmd!(shell, "cargo run --release --bin loadnext") - .env( - "L2_CHAIN_ID", - chain_config - .get_genesis_config()? - .l2_chain_id - .as_u64() - .to_string(), - ) - .env( - "MAIN_TOKEN", - format!( - "{:?}", - ecosystem_config - .get_erc20_tokens() - .first() - .context("NO Erc20 tokens were deployed")? - .address - ), - ) - .env("L2_RPC_ADDRESS", general_api.web3_json_rpc.http_url) - .env("L2_WS_RPC_ADDRESS", general_api.web3_json_rpc.ws_url); + let mut command = cmd!( + shell, + "cargo run --manifest-path ./core/Cargo.toml --release --bin loadnext" + ) + .env( + "L2_CHAIN_ID", + chain_config + .get_genesis_config()? + .l2_chain_id + .as_u64() + .to_string(), + ) + .env( + "MAIN_TOKEN", + format!( + "{:?}", + ecosystem_config + .get_erc20_tokens() + .first() + .context("NO Erc20 tokens were deployed")? + .address + ), + ) + .env("L2_RPC_ADDRESS", general_api.web3_json_rpc.http_url) + .env("L2_WS_RPC_ADDRESS", general_api.web3_json_rpc.ws_url); if global_config().verbose { command = command.env("RUST_LOG", "loadnext=info") diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs index c415eb7407d2..dce3cd9022da 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs @@ -59,7 +59,7 @@ pub async fn run(shell: &Shell, args: RustArgs) -> anyhow::Result<()> { reset_test_databases(shell, &link_to_code, dals).await?; - let _dir_guard = shell.push_dir(&link_to_code); + let _dir_guard = shell.push_dir(link_to_code.join("core")); logger::info(MSG_USING_CARGO_NEXTEST); let cmd = cmd!(shell, "cargo nextest run --release"); diff --git a/zkstack_cli/crates/zkstack/src/commands/external_node/build.rs b/zkstack_cli/crates/zkstack/src/commands/external_node/build.rs index 8a4c4befe8db..581f08565162 100644 --- a/zkstack_cli/crates/zkstack/src/commands/external_node/build.rs +++ b/zkstack_cli/crates/zkstack/src/commands/external_node/build.rs @@ -10,7 +10,7 @@ pub(crate) async fn build(shell: &Shell) -> anyhow::Result<()> { let chain = ecosystem .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let _dir_guard = shell.push_dir(&chain.link_to_code); + let _dir_guard = shell.push_dir(chain.link_to_code.join("core")); logger::info(MSG_BUILDING_EN); diff --git a/zkstack_cli/crates/zkstack/src/commands/server.rs b/zkstack_cli/crates/zkstack/src/commands/server.rs index 4088a888d534..e1e4ca3ff99d 100644 --- a/zkstack_cli/crates/zkstack/src/commands/server.rs +++ b/zkstack_cli/crates/zkstack/src/commands/server.rs @@ -35,7 +35,7 @@ pub async fn run(shell: &Shell, args: ServerArgs) -> anyhow::Result<()> { } fn build_server(chain_config: &ChainConfig, shell: &Shell) -> anyhow::Result<()> { - let _dir_guard = shell.push_dir(&chain_config.link_to_code); + let _dir_guard = shell.push_dir(chain_config.link_to_code.join("core")); logger::info(MSG_BUILDING_SERVER); From 79f65780993d12b63fa2ed36a129c4f8f66624f9 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 16 Jan 2025 15:54:47 +0200 Subject: [PATCH 02/52] refactor(vm): Deduplicate code shared with legacy VM (#3477) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Deduplicates helper code shared between legacy and fast VMs. ## Why ❔ Less code is easier to maintain. ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- .../src/tracers/validator/vm_latest/mod.rs | 14 +- .../vm_fast/bootloader_state/l2_block.rs | 78 ---- .../versions/vm_fast/bootloader_state/mod.rs | 8 - .../vm_fast/bootloader_state/state.rs | 323 ----------------- .../versions/vm_fast/bootloader_state/tx.rs | 50 --- .../vm_fast/bootloader_state/utils.rs | 236 ------------ core/lib/multivm/src/versions/vm_fast/hook.rs | 39 -- .../vm_fast/initial_bootloader_memory.rs | 43 --- core/lib/multivm/src/versions/vm_fast/mod.rs | 5 - .../src/versions/vm_fast/transaction_data.rs | 340 ------------------ core/lib/multivm/src/versions/vm_fast/vm.rs | 59 ++- .../src/versions/vm_latest/bootloader/init.rs | 46 +++ .../l2_block.rs | 2 +- .../src/versions/vm_latest/bootloader/mod.rs | 9 + .../bootloader}/snapshot.rs | 0 .../{bootloader_state => bootloader}/state.rs | 4 +- .../{bootloader_state => bootloader}/tx.rs | 2 +- .../{bootloader_state => bootloader}/utils.rs | 2 +- .../vm_latest/bootloader_state/mod.rs | 8 - .../vm_latest/bootloader_state/snapshot.rs | 25 -- .../vm_latest/implementation/snapshots.rs | 2 +- .../versions/vm_latest/implementation/tx.rs | 2 +- .../lib/multivm/src/versions/vm_latest/mod.rs | 11 +- .../src/versions/vm_latest/old_vm/utils.rs | 2 +- .../src/versions/vm_latest/tests/mod.rs | 2 +- .../src/versions/vm_latest/tests/rollbacks.rs | 2 +- .../vm_latest/tracers/circuits_tracer.rs | 4 +- .../vm_latest/tracers/default_tracers.rs | 28 +- .../versions/vm_latest/tracers/dispatcher.rs | 3 +- .../vm_latest/tracers/evm_deploy_tracer.rs | 2 +- .../vm_latest/tracers/pubdata_tracer.rs | 10 +- .../src/versions/vm_latest/tracers/refunds.rs | 83 +---- .../vm_latest/tracers/result_tracer.rs | 9 +- .../src/versions/vm_latest/tracers/traits.rs | 4 +- .../src/versions/vm_latest/tracers/utils.rs | 69 +--- .../src/versions/vm_latest/types/hook.rs | 39 ++ .../versions/vm_latest/types/internals/mod.rs | 7 - .../src/versions/vm_latest/types/l1_batch.rs | 43 --- .../src/versions/vm_latest/types/mod.rs | 11 +- .../types/{internals => }/snapshot.rs | 2 +- .../types/{internals => }/transaction_data.rs | 0 .../types/{internals => }/vm_state.rs | 5 +- .../src/versions/vm_latest/utils/logs.rs | 2 +- .../src/versions/vm_latest/utils/mod.rs | 1 + .../{vm_fast => vm_latest/utils}/refund.rs | 0 .../vm_latest/utils/transaction_encoding.rs | 2 +- core/lib/multivm/src/versions/vm_latest/vm.rs | 4 +- 47 files changed, 222 insertions(+), 1420 deletions(-) delete mode 100644 core/lib/multivm/src/versions/vm_fast/bootloader_state/l2_block.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/bootloader_state/mod.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/bootloader_state/state.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/bootloader_state/tx.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/bootloader_state/utils.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/hook.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/initial_bootloader_memory.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/transaction_data.rs create mode 100644 core/lib/multivm/src/versions/vm_latest/bootloader/init.rs rename core/lib/multivm/src/versions/vm_latest/{bootloader_state => bootloader}/l2_block.rs (97%) create mode 100644 core/lib/multivm/src/versions/vm_latest/bootloader/mod.rs rename core/lib/multivm/src/versions/{vm_fast/bootloader_state => vm_latest/bootloader}/snapshot.rs (100%) rename core/lib/multivm/src/versions/vm_latest/{bootloader_state => bootloader}/state.rs (99%) rename core/lib/multivm/src/versions/vm_latest/{bootloader_state => bootloader}/tx.rs (98%) rename core/lib/multivm/src/versions/vm_latest/{bootloader_state => bootloader}/utils.rs (99%) delete mode 100644 core/lib/multivm/src/versions/vm_latest/bootloader_state/mod.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/bootloader_state/snapshot.rs create mode 100644 core/lib/multivm/src/versions/vm_latest/types/hook.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/types/internals/mod.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/types/l1_batch.rs rename core/lib/multivm/src/versions/vm_latest/types/{internals => }/snapshot.rs (82%) rename core/lib/multivm/src/versions/vm_latest/types/{internals => }/transaction_data.rs (100%) rename core/lib/multivm/src/versions/vm_latest/types/{internals => }/vm_state.rs (97%) rename core/lib/multivm/src/versions/{vm_fast => vm_latest/utils}/refund.rs (100%) diff --git a/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs b/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs index 5588dd144e95..5ad0ca025937 100644 --- a/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs +++ b/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs @@ -22,8 +22,8 @@ use crate::{ }, }, vm_latest::{ - tracers::utils::{computational_gas_price, get_calldata_page_via_abi, VmHook}, - BootloaderState, SimpleMemory, VmTracer, ZkSyncVmState, + tracers::utils::{computational_gas_price, get_calldata_page_via_abi}, + BootloaderState, SimpleMemory, VmHook, VmTracer, ZkSyncVmState, }, HistoryMode, }; @@ -205,25 +205,25 @@ impl DynTracer> let hook = VmHook::from_opcode_memory(&state, &data, self.vm_version.try_into().unwrap()); let current_mode = self.validation_mode; match (current_mode, hook) { - (ValidationTracerMode::NoValidation, VmHook::AccountValidationEntered) => { + (ValidationTracerMode::NoValidation, Some(VmHook::AccountValidationEntered)) => { // Account validation can be entered when there is no prior validation (i.e. "nested" validations are not allowed) self.validation_mode = ValidationTracerMode::UserTxValidation; } - (ValidationTracerMode::NoValidation, VmHook::PaymasterValidationEntered) => { + (ValidationTracerMode::NoValidation, Some(VmHook::PaymasterValidationEntered)) => { // Paymaster validation can be entered when there is no prior validation (i.e. "nested" validations are not allowed) self.validation_mode = ValidationTracerMode::PaymasterTxValidation; } - (_, VmHook::AccountValidationEntered | VmHook::PaymasterValidationEntered) => { + (_, Some(VmHook::AccountValidationEntered | VmHook::PaymasterValidationEntered)) => { panic!( "Unallowed transition inside the validation tracer. Mode: {:#?}, hook: {:#?}", self.validation_mode, hook ); } - (_, VmHook::NoValidationEntered) => { + (_, Some(VmHook::ValidationExited)) => { // Validation can be always turned off self.validation_mode = ValidationTracerMode::NoValidation; } - (_, VmHook::ValidationStepEndeded) => { + (_, Some(VmHook::ValidationStepEnded)) => { // The validation step has ended. self.should_stop_execution = true; } diff --git a/core/lib/multivm/src/versions/vm_fast/bootloader_state/l2_block.rs b/core/lib/multivm/src/versions/vm_fast/bootloader_state/l2_block.rs deleted file mode 100644 index 4f05ef30a46d..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/bootloader_state/l2_block.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::cmp::Ordering; - -use zksync_types::{web3::keccak256_concat, L2BlockNumber, H256}; - -use super::{snapshot::L2BlockSnapshot, tx::BootloaderTx}; -use crate::{ - interface::{L2Block, L2BlockEnv}, - vm_latest::utils::l2_blocks::l2_block_hash, -}; - -const EMPTY_TXS_ROLLING_HASH: H256 = H256::zero(); - -#[derive(Debug)] -pub(crate) struct BootloaderL2Block { - pub(crate) number: u32, - pub(crate) timestamp: u64, - pub(crate) txs_rolling_hash: H256, // The rolling hash of all the transactions in the miniblock - pub(crate) prev_block_hash: H256, - // Number of the first L2 block tx in L1 batch - pub(crate) first_tx_index: usize, - pub(crate) max_virtual_blocks_to_create: u32, - pub(crate) txs: Vec, -} - -impl BootloaderL2Block { - pub(crate) fn new(l2_block: L2BlockEnv, first_tx_place: usize) -> Self { - Self { - number: l2_block.number, - timestamp: l2_block.timestamp, - txs_rolling_hash: EMPTY_TXS_ROLLING_HASH, - prev_block_hash: l2_block.prev_block_hash, - first_tx_index: first_tx_place, - max_virtual_blocks_to_create: l2_block.max_virtual_blocks_to_create, - txs: vec![], - } - } - - pub(super) fn push_tx(&mut self, tx: BootloaderTx) { - self.update_rolling_hash(tx.hash); - self.txs.push(tx) - } - - pub(crate) fn get_hash(&self) -> H256 { - l2_block_hash( - L2BlockNumber(self.number), - self.timestamp, - self.prev_block_hash, - self.txs_rolling_hash, - ) - } - - fn update_rolling_hash(&mut self, tx_hash: H256) { - self.txs_rolling_hash = keccak256_concat(self.txs_rolling_hash, tx_hash) - } - - pub(crate) fn make_snapshot(&self) -> L2BlockSnapshot { - L2BlockSnapshot { - txs_rolling_hash: self.txs_rolling_hash, - txs_len: self.txs.len(), - } - } - - pub(crate) fn apply_snapshot(&mut self, snapshot: L2BlockSnapshot) { - self.txs_rolling_hash = snapshot.txs_rolling_hash; - match self.txs.len().cmp(&snapshot.txs_len) { - Ordering::Greater => self.txs.truncate(snapshot.txs_len), - Ordering::Less => panic!("Applying snapshot from future is not supported"), - Ordering::Equal => {} - } - } - pub(crate) fn l2_block(&self) -> L2Block { - L2Block { - number: self.number, - timestamp: self.timestamp, - hash: self.get_hash(), - } - } -} diff --git a/core/lib/multivm/src/versions/vm_fast/bootloader_state/mod.rs b/core/lib/multivm/src/versions/vm_fast/bootloader_state/mod.rs deleted file mode 100644 index 73830de2759b..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/bootloader_state/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod l2_block; -mod snapshot; -mod state; -mod tx; - -pub(crate) mod utils; -pub(crate) use snapshot::BootloaderStateSnapshot; -pub use state::BootloaderState; diff --git a/core/lib/multivm/src/versions/vm_fast/bootloader_state/state.rs b/core/lib/multivm/src/versions/vm_fast/bootloader_state/state.rs deleted file mode 100644 index be4f2881297d..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/bootloader_state/state.rs +++ /dev/null @@ -1,323 +0,0 @@ -use std::cmp::Ordering; - -use once_cell::sync::OnceCell; -use zksync_types::{vm::VmVersion, L2ChainId, ProtocolVersionId, U256}; - -use super::{ - l2_block::BootloaderL2Block, - tx::BootloaderTx, - utils::{apply_l2_block, apply_pubdata_to_memory, apply_tx_to_memory}, - BootloaderStateSnapshot, -}; -use crate::{ - interface::{ - pubdata::{PubdataBuilder, PubdataInput}, - BootloaderMemory, CompressedBytecodeInfo, L2BlockEnv, TxExecutionMode, - }, - versions::vm_fast::transaction_data::TransactionData, - vm_latest::{ - constants::get_tx_description_offset, utils::l2_blocks::assert_next_block, - MultiVmSubversion, - }, -}; - -/// Intermediate bootloader-related VM state. -/// -/// Required to process transactions one by one (since we intercept the VM execution to execute -/// transactions and add new ones to the memory on the fly). -/// Keeps tracking everything related to the bootloader memory and can restore the whole memory. -/// -/// -/// Serves two purposes: -/// - Tracks where next tx should be pushed to in the bootloader memory. -/// - Tracks which transaction should be executed next. -#[derive(Debug)] -pub struct BootloaderState { - /// ID of the next transaction to be executed. - /// See the structure doc-comment for a better explanation of purpose. - tx_to_execute: usize, - /// Stored txs in bootloader memory - l2_blocks: Vec, - /// The number of 32-byte words spent on the already included compressed bytecodes. - compressed_bytecodes_encoding: usize, - /// Initial memory of bootloader - initial_memory: BootloaderMemory, - /// Mode of txs for execution, it can be changed once per vm lunch - execution_mode: TxExecutionMode, - /// Current offset of the free space in the bootloader memory. - free_tx_offset: usize, - /// Information about the pubdata that will be needed to supply to the L1Messenger - pubdata_information: OnceCell, - /// Protocol version. - protocol_version: ProtocolVersionId, - /// Protocol subversion - subversion: MultiVmSubversion, -} - -impl BootloaderState { - pub(crate) fn new( - execution_mode: TxExecutionMode, - initial_memory: BootloaderMemory, - first_l2_block: L2BlockEnv, - protocol_version: ProtocolVersionId, - ) -> Self { - let l2_block = BootloaderL2Block::new(first_l2_block, 0); - Self { - tx_to_execute: 0, - compressed_bytecodes_encoding: 0, - l2_blocks: vec![l2_block], - initial_memory, - execution_mode, - free_tx_offset: 0, - pubdata_information: Default::default(), - protocol_version, - subversion: MultiVmSubversion::try_from(VmVersion::from(protocol_version)).unwrap(), - } - } - - pub(crate) fn set_refund_for_current_tx(&mut self, refund: u64) { - let current_tx = self.current_tx(); - // We can't set the refund for the latest tx or using the latest l2_block for fining tx - // Because we can fill the whole batch first and then execute txs one by one - let tx = self.find_tx_mut(current_tx); - tx.refund = refund; - } - - pub(crate) fn set_pubdata_input(&mut self, info: PubdataInput) { - self.pubdata_information - .set(info) - .expect("Pubdata information is already set"); - } - - pub(crate) fn start_new_l2_block(&mut self, l2_block: L2BlockEnv) { - let last_block = self.last_l2_block(); - assert!( - !last_block.txs.is_empty(), - "Can not create new miniblocks on top of empty ones" - ); - assert_next_block(&last_block.l2_block(), &l2_block); - self.push_l2_block(l2_block); - } - - /// This method bypass sanity checks and should be used carefully. - pub(crate) fn push_l2_block(&mut self, l2_block: L2BlockEnv) { - self.l2_blocks - .push(BootloaderL2Block::new(l2_block, self.free_tx_index())) - } - - pub(crate) fn push_tx( - &mut self, - tx: TransactionData, - predefined_overhead: u32, - predefined_refund: u64, - compressed_bytecodes: Vec, - trusted_ergs_limit: U256, - chain_id: L2ChainId, - ) -> BootloaderMemory { - let tx_offset = self.free_tx_offset(); - let bootloader_tx = BootloaderTx::new( - tx, - predefined_refund, - predefined_overhead, - trusted_ergs_limit, - compressed_bytecodes, - tx_offset, - chain_id, - ); - - let mut memory = vec![]; - let compressed_bytecode_size = apply_tx_to_memory( - &mut memory, - &bootloader_tx, - self.last_l2_block(), - self.free_tx_index(), - self.free_tx_offset(), - self.compressed_bytecodes_encoding, - self.execution_mode, - self.last_l2_block().txs.is_empty(), - self.subversion, - ); - self.compressed_bytecodes_encoding += compressed_bytecode_size; - self.free_tx_offset = tx_offset + bootloader_tx.encoded_len(); - self.last_mut_l2_block().push_tx(bootloader_tx); - memory - } - - pub(crate) fn last_l2_block(&self) -> &BootloaderL2Block { - self.l2_blocks.last().unwrap() - } - - pub(crate) fn get_pubdata_information(&self) -> &PubdataInput { - self.pubdata_information - .get() - .expect("Pubdata information is not set") - } - - pub(crate) fn settlement_layer_pubdata(&self, pubdata_builder: &dyn PubdataBuilder) -> Vec { - let pubdata_information = self - .pubdata_information - .get() - .expect("Pubdata information is not set"); - pubdata_builder.settlement_layer_pubdata(pubdata_information, self.protocol_version) - } - - fn last_mut_l2_block(&mut self) -> &mut BootloaderL2Block { - self.l2_blocks.last_mut().unwrap() - } - - /// Apply all bootloader transaction to the initial memory - pub(crate) fn bootloader_memory( - &self, - pubdata_builder: &dyn PubdataBuilder, - ) -> BootloaderMemory { - let mut initial_memory = self.initial_memory.clone(); - let mut offset = 0; - let mut compressed_bytecodes_offset = 0; - let mut tx_index = 0; - for l2_block in &self.l2_blocks { - for (num, tx) in l2_block.txs.iter().enumerate() { - let compressed_bytecodes_size = apply_tx_to_memory( - &mut initial_memory, - tx, - l2_block, - tx_index, - offset, - compressed_bytecodes_offset, - self.execution_mode, - num == 0, - self.subversion, - ); - offset += tx.encoded_len(); - compressed_bytecodes_offset += compressed_bytecodes_size; - tx_index += 1; - } - if l2_block.txs.is_empty() { - apply_l2_block(&mut initial_memory, l2_block, tx_index, self.subversion) - } - } - - let pubdata_information = self - .pubdata_information - .get() - .expect("Empty pubdata information"); - - apply_pubdata_to_memory( - &mut initial_memory, - pubdata_builder, - pubdata_information, - self.protocol_version, - self.subversion, - ); - initial_memory - } - - fn free_tx_offset(&self) -> usize { - self.free_tx_offset - } - - pub(crate) fn free_tx_index(&self) -> usize { - let l2_block = self.last_l2_block(); - l2_block.first_tx_index + l2_block.txs.len() - } - - pub(crate) fn get_last_tx_compressed_bytecodes(&self) -> &[CompressedBytecodeInfo] { - if let Some(tx) = self.last_l2_block().txs.last() { - &tx.compressed_bytecodes - } else { - &[] - } - } - - /// Returns the id of current tx - pub(crate) fn current_tx(&self) -> usize { - self.tx_to_execute - .checked_sub(1) - .expect("There are no current tx to execute") - } - - /// Returns the ID of the next transaction to be executed and increments the local transaction counter. - pub(crate) fn move_tx_to_execute_pointer(&mut self) -> usize { - assert!( - self.tx_to_execute < self.free_tx_index(), - "Attempt to execute tx that was not pushed to memory. Tx ID: {}, txs in bootloader: {}", - self.tx_to_execute, - self.free_tx_index() - ); - - let old = self.tx_to_execute; - self.tx_to_execute += 1; - old - } - - /// Get offset of tx description - pub(crate) fn get_tx_description_offset(&self, tx_index: usize) -> usize { - get_tx_description_offset(self.subversion) + self.find_tx(tx_index).offset - } - - pub(crate) fn insert_fictive_l2_block(&mut self) -> &BootloaderL2Block { - let block = self.last_l2_block(); - if !block.txs.is_empty() { - self.start_new_l2_block(L2BlockEnv { - timestamp: block.timestamp + 1, - number: block.number + 1, - prev_block_hash: block.get_hash(), - max_virtual_blocks_to_create: 1, - }); - } - self.last_l2_block() - } - - fn find_tx(&self, tx_index: usize) -> &BootloaderTx { - for block in self.l2_blocks.iter().rev() { - if tx_index >= block.first_tx_index { - return &block.txs[tx_index - block.first_tx_index]; - } - } - panic!("The tx with index {} must exist", tx_index) - } - - fn find_tx_mut(&mut self, tx_index: usize) -> &mut BootloaderTx { - for block in self.l2_blocks.iter_mut().rev() { - if tx_index >= block.first_tx_index { - return &mut block.txs[tx_index - block.first_tx_index]; - } - } - panic!("The tx with index {} must exist", tx_index) - } - - pub(crate) fn get_snapshot(&self) -> BootloaderStateSnapshot { - BootloaderStateSnapshot { - tx_to_execute: self.tx_to_execute, - l2_blocks_len: self.l2_blocks.len(), - last_l2_block: self.last_l2_block().make_snapshot(), - compressed_bytecodes_encoding: self.compressed_bytecodes_encoding, - free_tx_offset: self.free_tx_offset, - is_pubdata_information_provided: self.pubdata_information.get().is_some(), - } - } - - pub(crate) fn apply_snapshot(&mut self, snapshot: BootloaderStateSnapshot) { - self.tx_to_execute = snapshot.tx_to_execute; - self.compressed_bytecodes_encoding = snapshot.compressed_bytecodes_encoding; - self.free_tx_offset = snapshot.free_tx_offset; - match self.l2_blocks.len().cmp(&snapshot.l2_blocks_len) { - Ordering::Greater => self.l2_blocks.truncate(snapshot.l2_blocks_len), - Ordering::Less => panic!("Applying snapshot from future is not supported"), - Ordering::Equal => {} - } - self.last_mut_l2_block() - .apply_snapshot(snapshot.last_l2_block); - - if !snapshot.is_pubdata_information_provided { - self.pubdata_information = Default::default(); - } else { - // Under the correct usage of the snapshots of the bootloader state, - // this assertion should never fail, i.e. since the pubdata information - // can be set only once. However, we have this assertion just in case. - assert!( - self.pubdata_information.get().is_some(), - "Snapshot with no pubdata can not rollback to snapshot with one" - ); - } - } -} diff --git a/core/lib/multivm/src/versions/vm_fast/bootloader_state/tx.rs b/core/lib/multivm/src/versions/vm_fast/bootloader_state/tx.rs deleted file mode 100644 index dc0706561d5e..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/bootloader_state/tx.rs +++ /dev/null @@ -1,50 +0,0 @@ -use zksync_types::{L2ChainId, H256, U256}; - -use crate::{ - interface::CompressedBytecodeInfo, versions::vm_fast::transaction_data::TransactionData, -}; - -/// Information about tx necessary for execution in bootloader. -#[derive(Debug, Clone)] -pub(crate) struct BootloaderTx { - pub(crate) hash: H256, - /// Encoded transaction - pub(crate) encoded: Vec, - /// Compressed bytecodes, which has been published during this transaction - pub(crate) compressed_bytecodes: Vec, - /// Refunds for this transaction - pub(crate) refund: u64, - /// Gas overhead - pub(crate) gas_overhead: u32, - /// Gas Limit for this transaction. It can be different from the gas limit inside the transaction - pub(crate) trusted_gas_limit: U256, - /// Offset of the tx in bootloader memory - pub(crate) offset: usize, -} - -impl BootloaderTx { - pub(super) fn new( - tx: TransactionData, - predefined_refund: u64, - predefined_overhead: u32, - trusted_gas_limit: U256, - compressed_bytecodes: Vec, - offset: usize, - chain_id: L2ChainId, - ) -> Self { - let hash = tx.tx_hash(chain_id); - Self { - hash, - encoded: tx.into_tokens(), - compressed_bytecodes, - refund: predefined_refund, - gas_overhead: predefined_overhead, - trusted_gas_limit, - offset, - } - } - - pub(super) fn encoded_len(&self) -> usize { - self.encoded.len() - } -} diff --git a/core/lib/multivm/src/versions/vm_fast/bootloader_state/utils.rs b/core/lib/multivm/src/versions/vm_fast/bootloader_state/utils.rs deleted file mode 100644 index 8883fd33904b..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/bootloader_state/utils.rs +++ /dev/null @@ -1,236 +0,0 @@ -use zksync_types::{ethabi, h256_to_u256, ProtocolVersionId, U256}; - -use super::{l2_block::BootloaderL2Block, tx::BootloaderTx}; -use crate::{ - interface::{ - pubdata::{PubdataBuilder, PubdataInput}, - BootloaderMemory, CompressedBytecodeInfo, TxExecutionMode, - }, - utils::bytecode, - vm_latest::{ - constants::{ - get_bootloader_tx_description_offset, get_compressed_bytecodes_offset, - get_operator_provided_l1_messenger_pubdata_offset, get_operator_refunds_offset, - get_tx_description_offset, get_tx_operator_l2_block_info_offset, - get_tx_overhead_offset, get_tx_trusted_gas_limit_offset, - BOOTLOADER_TX_DESCRIPTION_SIZE, OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS, - TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO, - }, - MultiVmSubversion, - }, -}; - -pub(super) fn get_memory_for_compressed_bytecodes( - compressed_bytecodes: &[CompressedBytecodeInfo], -) -> Vec { - let memory_addition: Vec<_> = compressed_bytecodes - .iter() - .flat_map(bytecode::encode_call) - .collect(); - bytecode::bytes_to_be_words(&memory_addition) -} - -#[allow(clippy::too_many_arguments)] -pub(super) fn apply_tx_to_memory( - memory: &mut BootloaderMemory, - bootloader_tx: &BootloaderTx, - bootloader_l2_block: &BootloaderL2Block, - tx_index: usize, - tx_offset: usize, - compressed_bytecodes_size: usize, - execution_mode: TxExecutionMode, - start_new_l2_block: bool, - subversion: MultiVmSubversion, -) -> usize { - let bootloader_description_offset = get_bootloader_tx_description_offset(subversion) - + BOOTLOADER_TX_DESCRIPTION_SIZE * tx_index; - let tx_description_offset = get_tx_description_offset(subversion) + tx_offset; - - memory.push(( - bootloader_description_offset, - assemble_tx_meta(execution_mode, true), - )); - - memory.push(( - bootloader_description_offset + 1, - U256::from_big_endian(&(32 * tx_description_offset).to_be_bytes()), - )); - - let refund_offset = get_operator_refunds_offset(subversion) + tx_index; - memory.push((refund_offset, bootloader_tx.refund.into())); - - let overhead_offset = get_tx_overhead_offset(subversion) + tx_index; - memory.push((overhead_offset, bootloader_tx.gas_overhead.into())); - - let trusted_gas_limit_offset = get_tx_trusted_gas_limit_offset(subversion) + tx_index; - memory.push((trusted_gas_limit_offset, bootloader_tx.trusted_gas_limit)); - - memory.extend( - (tx_description_offset..tx_description_offset + bootloader_tx.encoded_len()) - .zip(bootloader_tx.encoded.clone()), - ); - apply_l2_block_inner( - memory, - bootloader_l2_block, - tx_index, - start_new_l2_block, - subversion, - ); - - // Note, +1 is moving for pointer - let compressed_bytecodes_offset = - get_compressed_bytecodes_offset(subversion) + 1 + compressed_bytecodes_size; - - let encoded_compressed_bytecodes = - get_memory_for_compressed_bytecodes(&bootloader_tx.compressed_bytecodes); - let compressed_bytecodes_encoding = encoded_compressed_bytecodes.len(); - - memory.extend( - (compressed_bytecodes_offset - ..compressed_bytecodes_offset + encoded_compressed_bytecodes.len()) - .zip(encoded_compressed_bytecodes), - ); - compressed_bytecodes_encoding -} - -pub(crate) fn apply_l2_block( - memory: &mut BootloaderMemory, - bootloader_l2_block: &BootloaderL2Block, - txs_index: usize, - subversion: MultiVmSubversion, -) { - apply_l2_block_inner(memory, bootloader_l2_block, txs_index, true, subversion) -} - -fn apply_l2_block_inner( - memory: &mut BootloaderMemory, - bootloader_l2_block: &BootloaderL2Block, - txs_index: usize, - start_new_l2_block: bool, - subversion: MultiVmSubversion, -) { - // Since L2 block information start from the `TX_OPERATOR_L2_BLOCK_INFO_OFFSET` and each - // L2 block info takes `TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO` slots, the position where the L2 block info - // for this transaction needs to be written is: - - let block_position = get_tx_operator_l2_block_info_offset(subversion) - + txs_index * TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO; - - memory.extend(vec![ - (block_position, bootloader_l2_block.number.into()), - (block_position + 1, bootloader_l2_block.timestamp.into()), - ( - block_position + 2, - h256_to_u256(bootloader_l2_block.prev_block_hash), - ), - ( - block_position + 3, - if start_new_l2_block { - bootloader_l2_block.max_virtual_blocks_to_create.into() - } else { - U256::zero() - }, - ), - ]) -} - -fn bootloader_memory_input( - pubdata_builder: &dyn PubdataBuilder, - input: &PubdataInput, - protocol_version: ProtocolVersionId, -) -> Vec { - let l2_da_validator_address = pubdata_builder.l2_da_validator(); - let operator_input = pubdata_builder.l1_messenger_operator_input(input, protocol_version); - ethabi::encode(&[ - ethabi::Token::Address(l2_da_validator_address), - ethabi::Token::Bytes(operator_input), - ]) -} - -pub(crate) fn apply_pubdata_to_memory( - memory: &mut BootloaderMemory, - pubdata_builder: &dyn PubdataBuilder, - pubdata_information: &PubdataInput, - protocol_version: ProtocolVersionId, - subversion: MultiVmSubversion, -) { - let (l1_messenger_pubdata_start_slot, pubdata) = match subversion { - MultiVmSubversion::SmallBootloaderMemory | MultiVmSubversion::IncreasedBootloaderMemory => { - // Skipping two slots as they will be filled by the bootloader itself: - // - One slot is for the selector of the call to the L1Messenger. - // - The other slot is for the 0x20 offset for the calldata. - let l1_messenger_pubdata_start_slot = - get_operator_provided_l1_messenger_pubdata_offset(subversion) + 2; - - // Need to skip first word as it represents array offset - // while bootloader expects only [len || data] - let pubdata = ethabi::encode(&[ethabi::Token::Bytes( - pubdata_builder.l1_messenger_operator_input(pubdata_information, protocol_version), - )])[32..] - .to_vec(); - - assert!( - pubdata.len() / 32 <= OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS - 2, - "The encoded pubdata is too big" - ); - - (l1_messenger_pubdata_start_slot, pubdata) - } - MultiVmSubversion::Gateway => { - // Skipping the first slot as it will be filled by the bootloader itself: - // It is for the selector of the call to the L1Messenger. - let l1_messenger_pubdata_start_slot = - get_operator_provided_l1_messenger_pubdata_offset(subversion) + 1; - - let pubdata = - bootloader_memory_input(pubdata_builder, pubdata_information, protocol_version); - - assert!( - // Note that unlike the previous version, the difference is `1`, since now it also includes the offset - pubdata.len() / 32 < OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS, - "The encoded pubdata is too big" - ); - - (l1_messenger_pubdata_start_slot, pubdata) - } - }; - - pubdata - .chunks(32) - .enumerate() - .for_each(|(slot_offset, value)| { - memory.push(( - l1_messenger_pubdata_start_slot + slot_offset, - U256::from(value), - )) - }); -} - -/// Forms a word that contains meta information for the transaction execution. -/// -/// # Current layout -/// -/// - 0 byte (MSB): server-side tx execution mode -/// In the server, we may want to execute different parts of the transaction in the different context -/// For example, when checking validity, we don't want to actually execute transaction and have side effects. -/// -/// Possible values: -/// - 0x00: validate & execute (normal mode) -/// - 0x02: execute but DO NOT validate -/// -/// - 31 byte (LSB): whether to execute transaction or not (at all). -pub(super) fn assemble_tx_meta(execution_mode: TxExecutionMode, execute_tx: bool) -> U256 { - let mut output = [0u8; 32]; - - // Set 0 byte (execution mode) - output[0] = match execution_mode { - TxExecutionMode::VerifyExecute => 0x00, - TxExecutionMode::EstimateFee => 0x00, - TxExecutionMode::EthCall => 0x02, - }; - - // Set 31 byte (marker for tx execution) - output[31] = u8::from(execute_tx); - - U256::from_big_endian(&output) -} diff --git a/core/lib/multivm/src/versions/vm_fast/hook.rs b/core/lib/multivm/src/versions/vm_fast/hook.rs deleted file mode 100644 index b138c6d496d9..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/hook.rs +++ /dev/null @@ -1,39 +0,0 @@ -#[derive(Debug, Copy, Clone)] -pub(crate) enum Hook { - AccountValidationEntered, - PaymasterValidationEntered, - ValidationExited, - ValidationStepEnded, - TxHasEnded, - DebugLog, - DebugReturnData, - NearCallCatch, - AskOperatorForRefund, - NotifyAboutRefund, - PostResult, - FinalBatchInfo, - PubdataRequested, -} - -impl Hook { - /// # Panics - /// Panics if the number does not correspond to any hook. - pub fn from_u32(hook: u32) -> Self { - match hook { - 0 => Hook::AccountValidationEntered, - 1 => Hook::PaymasterValidationEntered, - 2 => Hook::ValidationExited, - 3 => Hook::ValidationStepEnded, - 4 => Hook::TxHasEnded, - 5 => Hook::DebugLog, - 6 => Hook::DebugReturnData, - 7 => Hook::NearCallCatch, - 8 => Hook::AskOperatorForRefund, - 9 => Hook::NotifyAboutRefund, - 10 => Hook::PostResult, - 11 => Hook::FinalBatchInfo, - 12 => Hook::PubdataRequested, - _ => panic!("Unknown hook {}", hook), - } - } -} diff --git a/core/lib/multivm/src/versions/vm_fast/initial_bootloader_memory.rs b/core/lib/multivm/src/versions/vm_fast/initial_bootloader_memory.rs deleted file mode 100644 index 89b22d328ac5..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/initial_bootloader_memory.rs +++ /dev/null @@ -1,43 +0,0 @@ -use zksync_types::{address_to_u256, h256_to_u256, U256}; - -use crate::{interface::L1BatchEnv, vm_latest::utils::fee::get_batch_base_fee}; - -const OPERATOR_ADDRESS_SLOT: usize = 0; -const PREV_BLOCK_HASH_SLOT: usize = 1; -const NEW_BLOCK_TIMESTAMP_SLOT: usize = 2; -const NEW_BLOCK_NUMBER_SLOT: usize = 3; -const FAIR_PUBDATA_PRICE_SLOT: usize = 4; -const FAIR_L2_GAS_PRICE_SLOT: usize = 5; -const EXPECTED_BASE_FEE_SLOT: usize = 6; -const SHOULD_SET_NEW_BLOCK_SLOT: usize = 7; - -/// Returns the initial memory for the bootloader based on the current batch environment. -pub(crate) fn bootloader_initial_memory(l1_batch: &L1BatchEnv) -> Vec<(usize, U256)> { - let (prev_block_hash, should_set_new_block) = l1_batch - .previous_batch_hash - .map(|prev_block_hash| (h256_to_u256(prev_block_hash), U256::one())) - .unwrap_or_default(); - - vec![ - ( - OPERATOR_ADDRESS_SLOT, - address_to_u256(&l1_batch.fee_account), - ), - (PREV_BLOCK_HASH_SLOT, prev_block_hash), - (NEW_BLOCK_TIMESTAMP_SLOT, U256::from(l1_batch.timestamp)), - (NEW_BLOCK_NUMBER_SLOT, U256::from(l1_batch.number.0)), - ( - FAIR_PUBDATA_PRICE_SLOT, - U256::from(l1_batch.fee_input.fair_pubdata_price()), - ), - ( - FAIR_L2_GAS_PRICE_SLOT, - U256::from(l1_batch.fee_input.fair_l2_gas_price()), - ), - ( - EXPECTED_BASE_FEE_SLOT, - U256::from(get_batch_base_fee(l1_batch)), - ), - (SHOULD_SET_NEW_BLOCK_SLOT, should_set_new_block), - ] -} diff --git a/core/lib/multivm/src/versions/vm_fast/mod.rs b/core/lib/multivm/src/versions/vm_fast/mod.rs index dca575138553..58e7ad4b006c 100644 --- a/core/lib/multivm/src/versions/vm_fast/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/mod.rs @@ -6,17 +6,12 @@ pub use self::{ vm::Vm, }; -mod bootloader_state; mod bytecode; mod events; mod glue; -mod hook; -mod initial_bootloader_memory; -mod refund; #[cfg(test)] mod tests; mod tracers; -mod transaction_data; mod utils; mod version; mod vm; diff --git a/core/lib/multivm/src/versions/vm_fast/transaction_data.rs b/core/lib/multivm/src/versions/vm_fast/transaction_data.rs deleted file mode 100644 index 02697beee341..000000000000 --- a/core/lib/multivm/src/versions/vm_fast/transaction_data.rs +++ /dev/null @@ -1,340 +0,0 @@ -use std::convert::TryInto; - -use zksync_types::{ - address_to_h256, - bytecode::BytecodeHash, - ethabi::{encode, Address, Token}, - fee::{encoding_len, Fee}, - h256_to_u256, - l1::is_l1_tx_type, - l2::{L2Tx, TransactionType}, - transaction_request::{PaymasterParams, TransactionRequest}, - web3::Bytes, - Execute, ExecuteTransactionCommon, L2ChainId, L2TxCommonData, Nonce, Transaction, H256, U256, -}; - -use crate::{ - utils::bytecode::bytes_to_be_words, - vm_latest::{ - constants::{MAX_GAS_PER_PUBDATA_BYTE, TX_MAX_COMPUTE_GAS_LIMIT}, - utils::overhead::derive_overhead, - }, -}; - -/// This structure represents the data that is used by -/// the Bootloader to describe the transaction. -#[derive(Debug, Default, Clone)] -pub(crate) struct TransactionData { - pub(crate) tx_type: u8, - pub(crate) from: Address, - pub(crate) to: Option
, - pub(crate) gas_limit: U256, - pub(crate) pubdata_price_limit: U256, - pub(crate) max_fee_per_gas: U256, - pub(crate) max_priority_fee_per_gas: U256, - pub(crate) paymaster: Address, - pub(crate) nonce: U256, - pub(crate) value: U256, - // The reserved fields that are unique for different types of transactions. - // E.g. nonce is currently used in all transaction, but it should not be mandatory - // in the long run. - pub(crate) reserved: [U256; 4], - pub(crate) data: Vec, - pub(crate) signature: Vec, - // The factory deps provided with the transaction. - // Note that *only hashes* of these bytecodes are signed by the user - // and they are used in the ABI encoding of the struct. - // TODO: include this into the tx signature as part of SMA-1010 - pub(crate) factory_deps: Vec>, - pub(crate) paymaster_input: Vec, - pub(crate) reserved_dynamic: Vec, - pub(crate) raw_bytes: Option>, -} - -impl From for TransactionData { - fn from(execute_tx: Transaction) -> Self { - match execute_tx.common_data { - ExecuteTransactionCommon::L2(common_data) => { - let nonce = U256::from_big_endian(&common_data.nonce.to_be_bytes()); - - let should_check_chain_id = if matches!( - common_data.transaction_type, - TransactionType::LegacyTransaction - ) && common_data.extract_chain_id().is_some() - { - U256([1, 0, 0, 0]) - } else { - U256::zero() - }; - - // Ethereum transactions do not sign gas per pubdata limit, and so for them we need to use - // some default value. We use the maximum possible value that is allowed by the bootloader - // (i.e. we can not use u64::MAX, because the bootloader requires gas per pubdata for such - // transactions to be higher than `MAX_GAS_PER_PUBDATA_BYTE`). - let gas_per_pubdata_limit = if common_data.transaction_type.is_ethereum_type() { - MAX_GAS_PER_PUBDATA_BYTE.into() - } else { - common_data.fee.gas_per_pubdata_limit - }; - - TransactionData { - tx_type: (common_data.transaction_type as u32) as u8, - from: common_data.initiator_address, - to: execute_tx.execute.contract_address, - gas_limit: common_data.fee.gas_limit, - pubdata_price_limit: gas_per_pubdata_limit, - max_fee_per_gas: common_data.fee.max_fee_per_gas, - max_priority_fee_per_gas: common_data.fee.max_priority_fee_per_gas, - paymaster: common_data.paymaster_params.paymaster, - nonce, - value: execute_tx.execute.value, - reserved: [ - should_check_chain_id, - U256::zero(), - U256::zero(), - U256::zero(), - ], - data: execute_tx.execute.calldata, - signature: common_data.signature, - factory_deps: execute_tx.execute.factory_deps, - paymaster_input: common_data.paymaster_params.paymaster_input, - reserved_dynamic: vec![], - raw_bytes: execute_tx.raw_bytes.map(|a| a.0), - } - } - ExecuteTransactionCommon::L1(common_data) => { - let refund_recipient = h256_to_u256(address_to_h256(&common_data.refund_recipient)); - TransactionData { - tx_type: common_data.tx_format() as u8, - from: common_data.sender, - to: execute_tx.execute.contract_address, - gas_limit: common_data.gas_limit, - pubdata_price_limit: common_data.gas_per_pubdata_limit, - // It doesn't matter what we put here, since - // the bootloader does not charge anything - max_fee_per_gas: common_data.max_fee_per_gas, - max_priority_fee_per_gas: U256::zero(), - paymaster: Address::default(), - nonce: U256::from(common_data.serial_id.0), // priority op ID - value: execute_tx.execute.value, - reserved: [ - common_data.to_mint, - refund_recipient, - U256::zero(), - U256::zero(), - ], - data: execute_tx.execute.calldata, - // The signature isn't checked for L1 transactions so we don't care - signature: vec![], - factory_deps: execute_tx.execute.factory_deps, - paymaster_input: vec![], - reserved_dynamic: vec![], - raw_bytes: None, - } - } - ExecuteTransactionCommon::ProtocolUpgrade(common_data) => { - let refund_recipient = h256_to_u256(address_to_h256(&common_data.refund_recipient)); - TransactionData { - tx_type: common_data.tx_format() as u8, - from: common_data.sender, - to: execute_tx.execute.contract_address, - gas_limit: common_data.gas_limit, - pubdata_price_limit: common_data.gas_per_pubdata_limit, - // It doesn't matter what we put here, since - // the bootloader does not charge anything - max_fee_per_gas: common_data.max_fee_per_gas, - max_priority_fee_per_gas: U256::zero(), - paymaster: Address::default(), - nonce: U256::from(common_data.upgrade_id as u16), - value: execute_tx.execute.value, - reserved: [ - common_data.to_mint, - refund_recipient, - U256::zero(), - U256::zero(), - ], - data: execute_tx.execute.calldata, - // The signature isn't checked for L1 transactions so we don't care - signature: vec![], - factory_deps: execute_tx.execute.factory_deps, - paymaster_input: vec![], - reserved_dynamic: vec![], - raw_bytes: None, - } - } - } - } -} - -impl TransactionData { - pub(crate) fn abi_encode_with_custom_factory_deps( - self, - factory_deps_hashes: Vec, - ) -> Vec { - encode(&[Token::Tuple(vec![ - Token::Uint(U256::from_big_endian(&self.tx_type.to_be_bytes())), - Token::Address(self.from), - Token::Address(self.to.unwrap_or_default()), - Token::Uint(self.gas_limit), - Token::Uint(self.pubdata_price_limit), - Token::Uint(self.max_fee_per_gas), - Token::Uint(self.max_priority_fee_per_gas), - Token::Address(self.paymaster), - Token::Uint(self.nonce), - Token::Uint(self.value), - Token::FixedArray(self.reserved.iter().copied().map(Token::Uint).collect()), - Token::Bytes(self.data), - Token::Bytes(self.signature), - Token::Array(factory_deps_hashes.into_iter().map(Token::Uint).collect()), - Token::Bytes(self.paymaster_input), - Token::Bytes(self.reserved_dynamic), - ])]) - } - - pub(crate) fn abi_encode(self) -> Vec { - let factory_deps_hashes = self - .factory_deps - .iter() - .map(|dep| BytecodeHash::for_bytecode(dep).value_u256()) - .collect(); - self.abi_encode_with_custom_factory_deps(factory_deps_hashes) - } - - pub(crate) fn into_tokens(self) -> Vec { - bytes_to_be_words(&self.abi_encode()) - } - - pub(crate) fn overhead_gas(&self) -> u32 { - let encoded_len = encoding_len( - self.data.len() as u64, - self.signature.len() as u64, - self.factory_deps.len() as u64, - self.paymaster_input.len() as u64, - self.reserved_dynamic.len() as u64, - ); - - derive_overhead(encoded_len) - } - - pub(crate) fn trusted_ergs_limit(&self) -> U256 { - // No transaction is allowed to spend more than `TX_MAX_COMPUTE_GAS_LIMIT` gas on compute. - U256::from(TX_MAX_COMPUTE_GAS_LIMIT).min(self.gas_limit) - } - - pub(crate) fn tx_hash(&self, chain_id: L2ChainId) -> H256 { - if is_l1_tx_type(self.tx_type) { - return self.canonical_l1_tx_hash().unwrap(); - } - - let l2_tx: L2Tx = self.clone().try_into().unwrap(); - let mut transaction_request: TransactionRequest = l2_tx.into(); - transaction_request.chain_id = Some(chain_id.as_u64()); - - // It is assumed that the `TransactionData` always has all the necessary components to recover the hash. - transaction_request - .get_tx_hash() - .expect("Could not recover L2 transaction hash") - } - - fn canonical_l1_tx_hash(&self) -> Result { - use zksync_types::web3::keccak256; - - if !is_l1_tx_type(self.tx_type) { - return Err(TxHashCalculationError::CannotCalculateL1HashForL2Tx); - } - - let encoded_bytes = self.clone().abi_encode(); - - Ok(H256(keccak256(&encoded_bytes))) - } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum TxHashCalculationError { - CannotCalculateL1HashForL2Tx, - CannotCalculateL2HashForL1Tx, -} - -impl TryInto for TransactionData { - type Error = TxHashCalculationError; - - fn try_into(self) -> Result { - if is_l1_tx_type(self.tx_type) { - return Err(TxHashCalculationError::CannotCalculateL2HashForL1Tx); - } - - let common_data = L2TxCommonData { - transaction_type: (self.tx_type as u32).try_into().unwrap(), - nonce: Nonce(self.nonce.as_u32()), - fee: Fee { - max_fee_per_gas: self.max_fee_per_gas, - max_priority_fee_per_gas: self.max_priority_fee_per_gas, - gas_limit: self.gas_limit, - gas_per_pubdata_limit: self.pubdata_price_limit, - }, - signature: self.signature, - input: None, - initiator_address: self.from, - paymaster_params: PaymasterParams { - paymaster: self.paymaster, - paymaster_input: self.paymaster_input, - }, - }; - let execute = Execute { - contract_address: self.to, - value: self.value, - calldata: self.data, - factory_deps: self.factory_deps, - }; - - Ok(L2Tx { - execute, - common_data, - received_timestamp_ms: 0, - raw_bytes: self.raw_bytes.map(Bytes::from), - }) - } -} - -#[cfg(test)] -mod tests { - use zksync_types::fee::encoding_len; - - use super::*; - - #[test] - fn test_consistency_with_encoding_length() { - let transaction = TransactionData { - tx_type: 113, - from: Address::random(), - to: Some(Address::random()), - gas_limit: U256::from(1u32), - pubdata_price_limit: U256::from(1u32), - max_fee_per_gas: U256::from(1u32), - max_priority_fee_per_gas: U256::from(1u32), - paymaster: Address::random(), - nonce: U256::zero(), - value: U256::zero(), - // The reserved fields that are unique for different types of transactions. - // E.g. nonce is currently used in all transaction, but it should not be mandatory - // in the long run. - reserved: [U256::zero(); 4], - data: vec![0u8; 65], - signature: vec![0u8; 75], - // The factory deps provided with the transaction. - // Note that *only hashes* of these bytecodes are signed by the user - // and they are used in the ABI encoding of the struct. - // TODO: include this into the tx signature as part of SMA-1010 - factory_deps: vec![vec![0u8; 32], vec![1u8; 32]], - paymaster_input: vec![0u8; 85], - reserved_dynamic: vec![0u8; 32], - raw_bytes: None, - }; - - let assumed_encoded_len = encoding_len(65, 75, 2, 85, 32); - - let true_encoding_len = transaction.into_tokens().len(); - - assert_eq!(assumed_encoded_len, true_encoding_len); - } -} diff --git a/core/lib/multivm/src/versions/vm_fast/vm.rs b/core/lib/multivm/src/versions/vm_fast/vm.rs index 6b14409a2e08..5065b8a7c67d 100644 --- a/core/lib/multivm/src/versions/vm_fast/vm.rs +++ b/core/lib/multivm/src/versions/vm_fast/vm.rs @@ -25,12 +25,8 @@ use zksync_vm2::{ }; use super::{ - bootloader_state::{BootloaderState, BootloaderStateSnapshot}, bytecode::compress_bytecodes, - hook::Hook, - initial_bootloader_memory::bootloader_initial_memory, tracers::{DynamicBytecodes, ValidationTracer, WithBuiltinTracers}, - transaction_data::TransactionData, }; use crate::{ glue::GlueInto, @@ -44,16 +40,19 @@ use crate::{ VmInterfaceHistoryEnabled, VmRevertReason, VmTrackingContracts, }, utils::events::extract_l2tol1logs_from_l1_messenger, - vm_fast::{ - bootloader_state::utils::{apply_l2_block, apply_pubdata_to_memory}, - events::merge_events, - refund::compute_refund, - version::FastVmVersion, - }, - vm_latest::constants::{ - get_operator_refunds_offset, get_result_success_first_slot, - get_vm_hook_params_start_position, get_vm_hook_position, TX_GAS_LIMIT_OFFSET, - VM_HOOK_PARAMS_COUNT, + vm_fast::{events::merge_events, version::FastVmVersion}, + vm_latest::{ + bootloader::{ + utils::{apply_l2_block, apply_pubdata_to_memory}, + BootloaderState, BootloaderStateSnapshot, + }, + constants::{ + get_operator_refunds_offset, get_result_success_first_slot, + get_vm_hook_params_start_position, get_vm_hook_position, TX_GAS_LIMIT_OFFSET, + VM_HOOK_PARAMS_COUNT, + }, + utils::refund::compute_refund, + TransactionData, VmHook, }, VmVersion, }; @@ -136,7 +135,7 @@ impl Vm { &system_env.base_system_smart_contracts.bootloader, true, ); - let bootloader_memory = bootloader_initial_memory(&batch_env); + let bootloader_memory = BootloaderState::initial_memory(&batch_env); let mut inner = VirtualMachine::new( BOOTLOADER_ADDRESS, @@ -258,11 +257,11 @@ impl Vm { pub(crate) fn push_transaction_inner( &mut self, - tx: zksync_types::Transaction, + tx: Transaction, refund: u64, with_compression: bool, ) { - let tx: TransactionData = tx.into(); + let tx = TransactionData::new(tx, false); let overhead = tx.overhead_gas(); self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); @@ -432,9 +431,9 @@ where } }; - let hook = Hook::from_u32(hook); + let hook = VmHook::new(hook); match hook { - Hook::AccountValidationEntered => { + VmHook::AccountValidationEntered => { assert!( account_validation_gas_split.is_none(), "Account validation can't be nested" @@ -453,7 +452,7 @@ where self.inner.current_frame().set_gas(gas_given); } - Hook::ValidationExited => { + VmHook::ValidationExited => { tracer.validation.validation_exited(); if let Some(AccountValidationGasSplit { @@ -467,13 +466,13 @@ where } } - Hook::ValidationStepEnded => { + VmHook::ValidationStepEnded => { if Val::STOP_AFTER_VALIDATION { break (ExecutionResult::Success { output: vec![] }, true); } } - Hook::TxHasEnded => { + VmHook::TxHasEnded => { if let VmExecutionMode::OneTx = execution_mode { // The bootloader may invoke `TxHasEnded` hook without posting a tx result previously. One case when this can happen // is estimating gas for L1 transactions, if a transaction runs out of gas during execution. @@ -493,7 +492,7 @@ where break (tx_result, false); } } - Hook::AskOperatorForRefund => { + VmHook::AskOperatorForRefund => { if track_refunds { let [bootloader_refund, gas_spent_on_pubdata, gas_per_pubdata_byte] = self.get_hook_params(); @@ -535,12 +534,12 @@ where .set_refund_for_current_tx(refund_value); } } - Hook::NotifyAboutRefund => { + VmHook::NotifyAboutRefund => { if track_refunds { refunds.gas_refunded = self.get_hook_params()[0].low_u64() } } - Hook::PostResult => { + VmHook::PostResult => { let result = self.get_hook_params()[0]; let value = self.get_hook_params()[1]; let fp = FatPointer::from(value); @@ -556,7 +555,7 @@ where } }); } - Hook::FinalBatchInfo => { + VmHook::FinalBatchInfo => { // set fictive l2 block let txs_index = self.bootloader_state.free_tx_index(); let l2_block = self.bootloader_state.insert_fictive_l2_block(); @@ -564,7 +563,7 @@ where apply_l2_block(&mut memory, l2_block, txs_index, self.vm_version.into()); self.write_to_bootloader_heap(memory); } - Hook::PubdataRequested => { + VmHook::PubdataRequested => { if !matches!(execution_mode, VmExecutionMode::Batch) { unreachable!("We do not provide the pubdata when executing the block tip or a single transaction"); } @@ -614,14 +613,14 @@ where self.write_to_bootloader_heap(memory_to_apply); } - Hook::PaymasterValidationEntered => { /* unused */ } - Hook::DebugLog => { + VmHook::PaymasterValidationEntered => { /* unused */ } + VmHook::DebugLog => { let (log, log_arg) = self.get_debug_log(); let last_tx = self.bootloader_state.last_l2_block().txs.last(); let tx_hash = last_tx.map(|tx| tx.hash); tracing::trace!(tx = ?tx_hash, "{log}: {log_arg}"); } - Hook::DebugReturnData | Hook::NearCallCatch => { + VmHook::DebugReturnData | VmHook::NearCallCatch => { // These hooks are for debug purposes only } } diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader/init.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/init.rs new file mode 100644 index 000000000000..7897ada6ad23 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/bootloader/init.rs @@ -0,0 +1,46 @@ +use zksync_types::{address_to_u256, h256_to_u256, U256}; + +use super::BootloaderState; +use crate::{interface::L1BatchEnv, vm_latest::utils::fee::get_batch_base_fee}; + +const OPERATOR_ADDRESS_SLOT: usize = 0; +const PREV_BLOCK_HASH_SLOT: usize = 1; +const NEW_BLOCK_TIMESTAMP_SLOT: usize = 2; +const NEW_BLOCK_NUMBER_SLOT: usize = 3; +const FAIR_PUBDATA_PRICE_SLOT: usize = 4; +const FAIR_L2_GAS_PRICE_SLOT: usize = 5; +const EXPECTED_BASE_FEE_SLOT: usize = 6; +const SHOULD_SET_NEW_BLOCK_SLOT: usize = 7; + +impl BootloaderState { + /// Returns the initial memory for the bootloader based on the current batch environment. + pub(crate) fn initial_memory(l1_batch: &L1BatchEnv) -> Vec<(usize, U256)> { + let (prev_block_hash, should_set_new_block) = l1_batch + .previous_batch_hash + .map(|prev_block_hash| (h256_to_u256(prev_block_hash), U256::one())) + .unwrap_or_default(); + + vec![ + ( + OPERATOR_ADDRESS_SLOT, + address_to_u256(&l1_batch.fee_account), + ), + (PREV_BLOCK_HASH_SLOT, prev_block_hash), + (NEW_BLOCK_TIMESTAMP_SLOT, U256::from(l1_batch.timestamp)), + (NEW_BLOCK_NUMBER_SLOT, U256::from(l1_batch.number.0)), + ( + FAIR_PUBDATA_PRICE_SLOT, + U256::from(l1_batch.fee_input.fair_pubdata_price()), + ), + ( + FAIR_L2_GAS_PRICE_SLOT, + U256::from(l1_batch.fee_input.fair_l2_gas_price()), + ), + ( + EXPECTED_BASE_FEE_SLOT, + U256::from(get_batch_base_fee(l1_batch)), + ), + (SHOULD_SET_NEW_BLOCK_SLOT, should_set_new_block), + ] + } +} diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader_state/l2_block.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/l2_block.rs similarity index 97% rename from core/lib/multivm/src/versions/vm_latest/bootloader_state/l2_block.rs rename to core/lib/multivm/src/versions/vm_latest/bootloader/l2_block.rs index 95502b8dc60c..922f5384ab3f 100644 --- a/core/lib/multivm/src/versions/vm_latest/bootloader_state/l2_block.rs +++ b/core/lib/multivm/src/versions/vm_latest/bootloader/l2_block.rs @@ -5,7 +5,7 @@ use zksync_types::{web3::keccak256_concat, L2BlockNumber, H256}; use crate::{ interface::{L2Block, L2BlockEnv}, vm_latest::{ - bootloader_state::{snapshot::L2BlockSnapshot, tx::BootloaderTx}, + bootloader::{snapshot::L2BlockSnapshot, tx::BootloaderTx}, utils::l2_blocks::l2_block_hash, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader/mod.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/mod.rs new file mode 100644 index 000000000000..02f67f322f0c --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/bootloader/mod.rs @@ -0,0 +1,9 @@ +pub(crate) use self::snapshot::BootloaderStateSnapshot; +pub use self::state::BootloaderState; + +mod init; +mod l2_block; +mod snapshot; +mod state; +mod tx; +pub(crate) mod utils; diff --git a/core/lib/multivm/src/versions/vm_fast/bootloader_state/snapshot.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/snapshot.rs similarity index 100% rename from core/lib/multivm/src/versions/vm_fast/bootloader_state/snapshot.rs rename to core/lib/multivm/src/versions/vm_latest/bootloader/snapshot.rs diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader_state/state.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/state.rs similarity index 99% rename from core/lib/multivm/src/versions/vm_latest/bootloader_state/state.rs rename to core/lib/multivm/src/versions/vm_latest/bootloader/state.rs index 8897ed2dc6e0..0b65d961d3ac 100644 --- a/core/lib/multivm/src/versions/vm_latest/bootloader_state/state.rs +++ b/core/lib/multivm/src/versions/vm_latest/bootloader/state.rs @@ -11,13 +11,13 @@ use crate::{ TxExecutionMode, }, vm_latest::{ - bootloader_state::{ + bootloader::{ l2_block::BootloaderL2Block, snapshot::BootloaderStateSnapshot, utils::{apply_l2_block, apply_tx_to_memory}, }, constants::get_tx_description_offset, - types::internals::TransactionData, + types::TransactionData, utils::l2_blocks::assert_next_block, MultiVmSubversion, }, diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader_state/tx.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/tx.rs similarity index 98% rename from core/lib/multivm/src/versions/vm_latest/bootloader_state/tx.rs rename to core/lib/multivm/src/versions/vm_latest/bootloader/tx.rs index 2c63db7e4354..00c7ae43c58f 100644 --- a/core/lib/multivm/src/versions/vm_latest/bootloader_state/tx.rs +++ b/core/lib/multivm/src/versions/vm_latest/bootloader/tx.rs @@ -1,6 +1,6 @@ use zksync_types::{L2ChainId, H256, U256}; -use crate::{interface::CompressedBytecodeInfo, vm_latest::types::internals::TransactionData}; +use crate::{interface::CompressedBytecodeInfo, vm_latest::types::TransactionData}; /// Information about tx necessary for execution in bootloader. #[derive(Debug, Clone)] diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader_state/utils.rs b/core/lib/multivm/src/versions/vm_latest/bootloader/utils.rs similarity index 99% rename from core/lib/multivm/src/versions/vm_latest/bootloader_state/utils.rs rename to core/lib/multivm/src/versions/vm_latest/bootloader/utils.rs index 77a8ef3a1a71..e309c6c45acb 100644 --- a/core/lib/multivm/src/versions/vm_latest/bootloader_state/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/bootloader/utils.rs @@ -8,7 +8,7 @@ use crate::{ }, utils::bytecode, vm_latest::{ - bootloader_state::l2_block::BootloaderL2Block, + bootloader::l2_block::BootloaderL2Block, constants::{ get_bootloader_tx_description_offset, get_compressed_bytecodes_offset, get_operator_provided_l1_messenger_pubdata_offset, get_operator_refunds_offset, diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader_state/mod.rs b/core/lib/multivm/src/versions/vm_latest/bootloader_state/mod.rs deleted file mode 100644 index 73830de2759b..000000000000 --- a/core/lib/multivm/src/versions/vm_latest/bootloader_state/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod l2_block; -mod snapshot; -mod state; -mod tx; - -pub(crate) mod utils; -pub(crate) use snapshot::BootloaderStateSnapshot; -pub use state::BootloaderState; diff --git a/core/lib/multivm/src/versions/vm_latest/bootloader_state/snapshot.rs b/core/lib/multivm/src/versions/vm_latest/bootloader_state/snapshot.rs deleted file mode 100644 index 8f1cec3cb7f1..000000000000 --- a/core/lib/multivm/src/versions/vm_latest/bootloader_state/snapshot.rs +++ /dev/null @@ -1,25 +0,0 @@ -use zksync_types::H256; - -#[derive(Debug, Clone)] -pub(crate) struct BootloaderStateSnapshot { - /// ID of the next transaction to be executed. - pub(crate) tx_to_execute: usize, - /// Stored L2 blocks in bootloader memory - pub(crate) l2_blocks_len: usize, - /// Snapshot of the last L2 block. Only this block could be changed during the rollback - pub(crate) last_l2_block: L2BlockSnapshot, - /// The number of 32-byte words spent on the already included compressed bytecodes. - pub(crate) compressed_bytecodes_encoding: usize, - /// Current offset of the free space in the bootloader memory. - pub(crate) free_tx_offset: usize, - /// Whether the pubdata information has been provided already - pub(crate) is_pubdata_information_provided: bool, -} - -#[derive(Debug, Clone)] -pub(crate) struct L2BlockSnapshot { - /// The rolling hash of all the transactions in the miniblock - pub(crate) txs_rolling_hash: H256, - /// The number of transactions in the last L2 block - pub(crate) txs_len: usize, -} diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs b/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs index 377c4f548b06..82ea5829342b 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs @@ -7,7 +7,7 @@ use crate::{ interface::storage::WriteStorage, vm_latest::{ old_vm::{history_recorder::HistoryEnabled, oracles::OracleWithHistory}, - types::internals::VmSnapshot, + types::VmSnapshot, vm::Vm, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs b/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs index 6dd73866adf2..02adcc3cdad8 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs @@ -6,7 +6,7 @@ use crate::{ vm_latest::{ constants::BOOTLOADER_HEAP_PAGE, implementation::bytecode::{bytecode_to_factory_dep, compress_bytecodes}, - types::internals::TransactionData, + types::TransactionData, vm::Vm, }, HistoryMode, diff --git a/core/lib/multivm/src/versions/vm_latest/mod.rs b/core/lib/multivm/src/versions/vm_latest/mod.rs index 46f8db789ddc..4e739efe9516 100644 --- a/core/lib/multivm/src/versions/vm_latest/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/mod.rs @@ -1,6 +1,5 @@ -pub(crate) use self::vm::MultiVmSubversion; pub use self::{ - bootloader_state::BootloaderState, + bootloader::BootloaderState, old_vm::{ history_recorder::{ AppDataFrameManagerWithHistory, HistoryDisabled, HistoryEnabled, HistoryMode, @@ -12,12 +11,16 @@ pub use self::{ dispatcher::TracerDispatcher, traits::{ToTracerPointer, TracerPointer, VmTracer}, }, - types::internals::ZkSyncVmState, + types::ZkSyncVmState, utils::transaction_encoding::TransactionVmExt, vm::Vm, }; +pub(crate) use self::{ + types::{TransactionData, VmHook}, + vm::MultiVmSubversion, +}; -mod bootloader_state; +pub(crate) mod bootloader; pub mod constants; mod implementation; mod old_vm; diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs index c020d1db000a..aa5155fc003b 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs @@ -10,7 +10,7 @@ use zksync_types::{Address, U256}; use crate::{ interface::storage::WriteStorage, - vm_latest::{old_vm::memory::SimpleMemory, types::internals::ZkSyncVmState, HistoryMode}, + vm_latest::{old_vm::memory::SimpleMemory, types::ZkSyncVmState, HistoryMode}, }; #[derive(Debug, Clone)] diff --git a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs index 0a89ddb0bf50..10750f6ffaaf 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -32,7 +32,7 @@ use crate::{ constants::BOOTLOADER_HEAP_PAGE, old_vm::{event_sink::InMemoryEventSink, history_recorder::HistoryRecorder}, tracers::PubdataTracer, - types::internals::TransactionData, + types::TransactionData, utils::logs::StorageLogQuery, AppDataFrameManagerWithHistory, HistoryMode, SimpleMemory, TracerDispatcher, }, diff --git a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs index f126a7f8fbdd..5b99fc4558ed 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -18,7 +18,7 @@ use crate::{ VmTesterBuilder, }, vm_latest::{ - types::internals::ZkSyncVmState, BootloaderState, HistoryEnabled, HistoryMode, + bootloader::BootloaderState, types::ZkSyncVmState, HistoryEnabled, HistoryMode, SimpleMemory, ToTracerPointer, Vm, VmTracer, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs index 6a47f3ae2fbe..059c9e67eeb3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs @@ -15,10 +15,10 @@ use crate::{ tracers::dynamic::vm_1_5_0::DynTracer, utils::CircuitCycleStatistic, vm_latest::{ - bootloader_state::BootloaderState, + bootloader::BootloaderState, old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, tracers::traits::VmTracer, - types::internals::ZkSyncVmState, + types::ZkSyncVmState, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs b/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs index 08ff79524b87..05afbbdb398c 100755 --- a/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs @@ -23,17 +23,17 @@ use crate::{ }, tracers::dynamic::vm_1_5_0::DynTracer, vm_latest::{ - bootloader_state::{utils::apply_l2_block, BootloaderState}, + bootloader::{utils::apply_l2_block, BootloaderState}, constants::BOOTLOADER_HEAP_PAGE, old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, tracers::{ dispatcher::TracerDispatcher, - utils::{computational_gas_price, print_debug_if_needed, VmHook}, + utils::{computational_gas_price, print_debug_log, print_debug_returndata}, CircuitsTracer, RefundsTracer, ResultTracer, }, - types::internals::ZkSyncVmState, + types::ZkSyncVmState, vm::MultiVmSubversion, - VmTracer, + VmHook, VmTracer, }, }; @@ -220,22 +220,18 @@ impl Tracer for DefaultExecutionTracer { } let hook = VmHook::from_opcode_memory(&state, &data, self.subversion); - print_debug_if_needed( - &hook, - &state, - memory, - self.result_tracer.get_latest_result_ptr(), - self.subversion, - ); - match hook { - VmHook::TxHasEnded if matches!(self.execution_mode, VmExecutionMode::OneTx) => { + Some(VmHook::TxHasEnded) if matches!(self.execution_mode, VmExecutionMode::OneTx) => { self.result_tracer.tx_finished_in_one_tx_mode = true; self.tx_has_been_processed = true; } - VmHook::NoValidationEntered => self.in_account_validation = false, - VmHook::AccountValidationEntered => self.in_account_validation = true, - VmHook::FinalBatchInfo => self.final_batch_info_requested = true, + Some(VmHook::ValidationExited) => self.in_account_validation = false, + Some(VmHook::AccountValidationEntered) => self.in_account_validation = true, + Some(VmHook::FinalBatchInfo) => self.final_batch_info_requested = true, + Some(VmHook::DebugLog) => print_debug_log(&state, memory, self.subversion), + Some(VmHook::DebugReturnData) => { + print_debug_returndata(memory, self.result_tracer.get_latest_result_ptr()) + } _ => {} } diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs b/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs index 3c3ef1173f53..41ff237551f4 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs @@ -9,7 +9,8 @@ use crate::{ }, tracers::dynamic::vm_1_5_0::DynTracer, vm_latest::{ - BootloaderState, HistoryMode, SimpleMemory, TracerPointer, VmTracer, ZkSyncVmState, + bootloader::BootloaderState, HistoryMode, SimpleMemory, TracerPointer, VmTracer, + ZkSyncVmState, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs index dd03a9427efa..70e055343e61 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/evm_deploy_tracer.rs @@ -19,7 +19,7 @@ use crate::{ }, tracers::dynamic::vm_1_5_0::DynTracer, utils::bytecode::bytes_to_be_words, - vm_latest::{BootloaderState, HistoryMode, SimpleMemory, ZkSyncVmState}, + vm_latest::{bootloader::BootloaderState, HistoryMode, SimpleMemory, ZkSyncVmState}, }; /// Tracer responsible for collecting information about EVM deploys and providing those diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs index 31309e6ff062..f1f41a2ef3d3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs @@ -27,14 +27,14 @@ use crate::{ }, }, vm_latest::{ - bootloader_state::{utils::apply_pubdata_to_memory, BootloaderState}, + bootloader::{utils::apply_pubdata_to_memory, BootloaderState}, constants::BOOTLOADER_HEAP_PAGE, old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, - tracers::{traits::VmTracer, utils::VmHook}, - types::internals::ZkSyncVmState, + tracers::traits::VmTracer, + types::ZkSyncVmState, utils::logs::collect_events_and_l1_system_logs_after_timestamp, vm::MultiVmSubversion, - StorageOracle, + StorageOracle, VmHook, }, }; @@ -207,7 +207,7 @@ impl DynTracer> for PubdataTracer { _storage: StoragePtr, ) { let hook = VmHook::from_opcode_memory(&state, &data, self.subversion); - if let VmHook::PubdataRequested = hook { + if matches!(hook, Some(VmHook::PubdataRequested)) { self.pubdata_info_requested = true; } } diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs b/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs index 2bd08e094327..071c0ac37718 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs @@ -5,7 +5,7 @@ use zk_evm_1_5_0::{ aux_structures::Timestamp, tracing::{BeforeExecutionData, VmLocalStateData}, }; -use zksync_types::{ceil_div_u256, H256, U256}; +use zksync_types::H256; use crate::{ interface::{ @@ -15,16 +15,14 @@ use crate::{ }, tracers::dynamic::vm_1_5_0::DynTracer, vm_latest::{ - bootloader_state::BootloaderState, + bootloader::BootloaderState, constants::{get_operator_refunds_offset, BOOTLOADER_HEAP_PAGE, TX_GAS_LIMIT_OFFSET}, old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, - tracers::{ - traits::VmTracer, - utils::{get_vm_hook_params, VmHook}, - }, - types::internals::ZkSyncVmState, - utils::fee::get_batch_base_fee, + tracers::{traits::VmTracer, utils::get_vm_hook_params}, + types::ZkSyncVmState, + utils::refund::compute_refund, vm::MultiVmSubversion, + VmHook, }, }; @@ -101,58 +99,15 @@ impl RefundsTracer { pubdata_published: u32, tx_hash: H256, ) -> u64 { - let total_gas_spent = tx_gas_limit - bootloader_refund; - - let gas_spent_on_computation = total_gas_spent - .checked_sub(gas_spent_on_pubdata) - .unwrap_or_else(|| { - tracing::error!( - "Gas spent on pubdata is greater than total gas spent. On pubdata: {}, total: {}", - gas_spent_on_pubdata, - total_gas_spent - ); - 0 - }); - - // For now, bootloader charges only for base fee. - let effective_gas_price = get_batch_base_fee(&self.l1_batch); - - let bootloader_eth_price_per_pubdata_byte = - U256::from(effective_gas_price) * U256::from(current_ergs_per_pubdata_byte); - - let fair_eth_price_per_pubdata_byte = - U256::from(self.l1_batch.fee_input.fair_pubdata_price()); - - // For now, L1 originated transactions are allowed to pay less than fair fee per pubdata, - // so we should take it into account. - let eth_price_per_pubdata_byte_for_calculation = std::cmp::min( - bootloader_eth_price_per_pubdata_byte, - fair_eth_price_per_pubdata_byte, - ); - - let fair_fee_eth = U256::from(gas_spent_on_computation) - * U256::from(self.l1_batch.fee_input.fair_l2_gas_price()) - + U256::from(pubdata_published) * eth_price_per_pubdata_byte_for_calculation; - let pre_paid_eth = U256::from(tx_gas_limit) * U256::from(effective_gas_price); - let refund_eth = pre_paid_eth.checked_sub(fair_fee_eth).unwrap_or_else(|| { - tracing::error!( - "Fair fee is greater than pre paid. Fair fee: {} wei, pre paid: {} wei", - fair_fee_eth, - pre_paid_eth - ); - U256::zero() - }); - - tracing::trace!( - "Fee benchmark for transaction with hash {}", - hex::encode(tx_hash.as_bytes()) - ); - tracing::trace!("Gas Limit: {}", tx_gas_limit); - tracing::trace!("Gas spent on computation: {}", gas_spent_on_computation); - tracing::trace!("Gas spent on pubdata: {}", gas_spent_on_pubdata); - tracing::trace!("Pubdata published: {}", pubdata_published); - - ceil_div_u256(refund_eth, effective_gas_price.into()).as_u64() + compute_refund( + &self.l1_batch, + bootloader_refund, + gas_spent_on_pubdata, + tx_gas_limit, + current_ergs_per_pubdata_byte, + pubdata_published, + tx_hash, + ) } pub(crate) fn pubdata_published(&self) -> u32 { @@ -171,16 +126,16 @@ impl DynTracer> for RefundsTracer { self.timestamp_before_cycle = Timestamp(state.vm_local_state.timestamp); let hook = VmHook::from_opcode_memory(&state, &data, self.subversion); match hook { - VmHook::NotifyAboutRefund => { - self.refund_gas = get_vm_hook_params(memory, self.subversion)[0].as_u64() + Some(VmHook::NotifyAboutRefund) => { + self.refund_gas = get_vm_hook_params(memory, self.subversion)[0].as_u64(); } - VmHook::AskOperatorForRefund => { + Some(VmHook::AskOperatorForRefund) => { self.pending_refund_request = Some(RefundRequest { refund: get_vm_hook_params(memory, self.subversion)[0].as_u64(), gas_spent_on_pubdata: get_vm_hook_params(memory, self.subversion)[1].as_u64(), used_gas_per_pubdata_byte: get_vm_hook_params(memory, self.subversion)[2] .as_u32(), - }) + }); } _ => {} } diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs index 80a3147f65d2..a90c7c8a018a 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs @@ -16,15 +16,16 @@ use crate::{ }, tracers::dynamic::vm_1_5_0::DynTracer, vm_latest::{ + bootloader::BootloaderState, constants::{get_result_success_first_slot, BOOTLOADER_HEAP_PAGE}, old_vm::utils::{vm_may_have_ended_inner, VmExecutionResult}, tracers::{ traits::VmTracer, - utils::{get_vm_hook_params, read_pointer, VmHook}, + utils::{get_vm_hook_params, read_pointer}, }, - types::internals::ZkSyncVmState, + types::ZkSyncVmState, vm::MultiVmSubversion, - BootloaderState, HistoryMode, SimpleMemory, + HistoryMode, SimpleMemory, VmHook, }, }; @@ -155,7 +156,7 @@ impl DynTracer> for ResultTracer { _storage: StoragePtr, ) { let hook = VmHook::from_opcode_memory(&state, &data, self.subversion); - if let VmHook::ExecutionResult = hook { + if matches!(hook, Some(VmHook::PostResult)) { let vm_hook_params = get_vm_hook_params(memory, self.subversion); let success = vm_hook_params[0]; let returndata = self diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs b/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs index 76dab3dd70a1..b85f8e3ca4e0 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs @@ -5,9 +5,9 @@ use crate::{ }, tracers::dynamic::vm_1_5_0::DynTracer, vm_latest::{ - bootloader_state::BootloaderState, + bootloader::BootloaderState, old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, - types::internals::ZkSyncVmState, + types::ZkSyncVmState, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs b/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs index 6f81a3ac8de5..ced3193ca490 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs @@ -22,33 +22,15 @@ use crate::vm_latest::{ utils::{aux_heap_page_from_base, heap_page_from_base}, }, vm::MultiVmSubversion, + VmHook, }; -#[derive(Clone, Debug, Copy)] -pub(crate) enum VmHook { - AccountValidationEntered, - PaymasterValidationEntered, - NoValidationEntered, - ValidationStepEndeded, - TxHasEnded, - DebugLog, - DebugReturnData, - NoHook, - NearCallCatch, - AskOperatorForRefund, - NotifyAboutRefund, - ExecutionResult, - FinalBatchInfo, - // Hook used to signal that the final pubdata for a batch is requested. - PubdataRequested, -} - impl VmHook { pub(crate) fn from_opcode_memory( state: &VmLocalStateData<'_>, data: &BeforeExecutionData, subversion: MultiVmSubversion, - ) -> Self { + ) -> Option { let opcode_variant = data.opcode.variant; let heap_page = heap_page_from_base(state.vm_local_state.callstack.current.base_memory_page).0; @@ -64,33 +46,18 @@ impl VmHook { || heap_page != BOOTLOADER_HEAP_PAGE || fat_ptr.offset != get_vm_hook_position(subversion) * 32 { - return Self::NoHook; + return None; } - match value.as_u32() { - 0 => Self::AccountValidationEntered, - 1 => Self::PaymasterValidationEntered, - 2 => Self::NoValidationEntered, - 3 => Self::ValidationStepEndeded, - 4 => Self::TxHasEnded, - 5 => Self::DebugLog, - 6 => Self::DebugReturnData, - 7 => Self::NearCallCatch, - 8 => Self::AskOperatorForRefund, - 9 => Self::NotifyAboutRefund, - 10 => Self::ExecutionResult, - 11 => Self::FinalBatchInfo, - 12 => Self::PubdataRequested, - _ => panic!("Unknown hook: {}", value.as_u32()), - } + Some(Self::new(value.as_u32())) } } -pub(crate) fn get_debug_log( +pub(crate) fn print_debug_log( state: &VmLocalStateData<'_>, memory: &SimpleMemory, subversion: MultiVmSubversion, -) -> String { +) { let vm_hook_params: Vec<_> = get_vm_hook_params(memory, subversion) .into_iter() .map(u256_to_h256) @@ -113,7 +80,7 @@ pub(crate) fn get_debug_log( data.to_string() }; let tx_id = state.vm_local_state.tx_number_in_block; - format!("Bootloader transaction {tx_id}: {msg}: {data_str}") + tracing::trace!("Bootloader transaction {tx_id}: {msg}: {data_str}"); } /// Reads the memory slice represented by the fat pointer. @@ -142,33 +109,17 @@ pub(crate) fn read_pointer( /// Outputs the returndata for the latest call. /// This is usually used to output the revert reason. -pub(crate) fn get_debug_returndata( +pub(crate) fn print_debug_returndata( memory: &SimpleMemory, latest_returndata_ptr: Option, -) -> String { +) { let returndata = if let Some(ptr) = latest_returndata_ptr { read_pointer(memory, ptr) } else { vec![] }; - format!("0x{}", hex::encode(returndata)) -} - -/// Accepts a vm hook and, if it requires to output some debug log, outputs it. -pub(crate) fn print_debug_if_needed( - hook: &VmHook, - state: &VmLocalStateData<'_>, - memory: &SimpleMemory, - latest_returndata_ptr: Option, - subversion: MultiVmSubversion, -) { - let log = match hook { - VmHook::DebugLog => get_debug_log(state, memory, subversion), - VmHook::DebugReturnData => get_debug_returndata(memory, latest_returndata_ptr), - _ => return, - }; - tracing::trace!("{log}"); + tracing::trace!("0x{}", hex::encode(returndata)); } pub(crate) fn computational_gas_price( diff --git a/core/lib/multivm/src/versions/vm_latest/types/hook.rs b/core/lib/multivm/src/versions/vm_latest/types/hook.rs new file mode 100644 index 000000000000..1fbd0c826e5e --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/types/hook.rs @@ -0,0 +1,39 @@ +#[derive(Debug, Copy, Clone)] +pub(crate) enum VmHook { + AccountValidationEntered, + PaymasterValidationEntered, + ValidationExited, + ValidationStepEnded, + TxHasEnded, + DebugLog, + DebugReturnData, + NearCallCatch, + AskOperatorForRefund, + NotifyAboutRefund, + PostResult, + FinalBatchInfo, + PubdataRequested, +} + +impl VmHook { + /// # Panics + /// Panics if the number does not correspond to any hook. + pub fn new(raw: u32) -> Self { + match raw { + 0 => Self::AccountValidationEntered, + 1 => Self::PaymasterValidationEntered, + 2 => Self::ValidationExited, + 3 => Self::ValidationStepEnded, + 4 => Self::TxHasEnded, + 5 => Self::DebugLog, + 6 => Self::DebugReturnData, + 7 => Self::NearCallCatch, + 8 => Self::AskOperatorForRefund, + 9 => Self::NotifyAboutRefund, + 10 => Self::PostResult, + 11 => Self::FinalBatchInfo, + 12 => Self::PubdataRequested, + _ => panic!("Unknown hook: {raw}"), + } + } +} diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/mod.rs b/core/lib/multivm/src/versions/vm_latest/types/internals/mod.rs deleted file mode 100644 index 601b7b8bd014..000000000000 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(crate) use snapshot::VmSnapshot; -pub(crate) use transaction_data::TransactionData; -pub(crate) use vm_state::new_vm_state; -pub use vm_state::ZkSyncVmState; -mod snapshot; -mod transaction_data; -mod vm_state; diff --git a/core/lib/multivm/src/versions/vm_latest/types/l1_batch.rs b/core/lib/multivm/src/versions/vm_latest/types/l1_batch.rs deleted file mode 100644 index 89b22d328ac5..000000000000 --- a/core/lib/multivm/src/versions/vm_latest/types/l1_batch.rs +++ /dev/null @@ -1,43 +0,0 @@ -use zksync_types::{address_to_u256, h256_to_u256, U256}; - -use crate::{interface::L1BatchEnv, vm_latest::utils::fee::get_batch_base_fee}; - -const OPERATOR_ADDRESS_SLOT: usize = 0; -const PREV_BLOCK_HASH_SLOT: usize = 1; -const NEW_BLOCK_TIMESTAMP_SLOT: usize = 2; -const NEW_BLOCK_NUMBER_SLOT: usize = 3; -const FAIR_PUBDATA_PRICE_SLOT: usize = 4; -const FAIR_L2_GAS_PRICE_SLOT: usize = 5; -const EXPECTED_BASE_FEE_SLOT: usize = 6; -const SHOULD_SET_NEW_BLOCK_SLOT: usize = 7; - -/// Returns the initial memory for the bootloader based on the current batch environment. -pub(crate) fn bootloader_initial_memory(l1_batch: &L1BatchEnv) -> Vec<(usize, U256)> { - let (prev_block_hash, should_set_new_block) = l1_batch - .previous_batch_hash - .map(|prev_block_hash| (h256_to_u256(prev_block_hash), U256::one())) - .unwrap_or_default(); - - vec![ - ( - OPERATOR_ADDRESS_SLOT, - address_to_u256(&l1_batch.fee_account), - ), - (PREV_BLOCK_HASH_SLOT, prev_block_hash), - (NEW_BLOCK_TIMESTAMP_SLOT, U256::from(l1_batch.timestamp)), - (NEW_BLOCK_NUMBER_SLOT, U256::from(l1_batch.number.0)), - ( - FAIR_PUBDATA_PRICE_SLOT, - U256::from(l1_batch.fee_input.fair_pubdata_price()), - ), - ( - FAIR_L2_GAS_PRICE_SLOT, - U256::from(l1_batch.fee_input.fair_l2_gas_price()), - ), - ( - EXPECTED_BASE_FEE_SLOT, - U256::from(get_batch_base_fee(l1_batch)), - ), - (SHOULD_SET_NEW_BLOCK_SLOT, should_set_new_block), - ] -} diff --git a/core/lib/multivm/src/versions/vm_latest/types/mod.rs b/core/lib/multivm/src/versions/vm_latest/types/mod.rs index a12005734abb..0963b7c458c0 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/mod.rs @@ -1,2 +1,9 @@ -pub(crate) mod internals; -mod l1_batch; +mod hook; +mod snapshot; +mod transaction_data; +mod vm_state; + +pub use self::vm_state::ZkSyncVmState; +pub(crate) use self::{ + hook::VmHook, snapshot::VmSnapshot, transaction_data::TransactionData, vm_state::new_vm_state, +}; diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs b/core/lib/multivm/src/versions/vm_latest/types/snapshot.rs similarity index 82% rename from core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs rename to core/lib/multivm/src/versions/vm_latest/types/snapshot.rs index 76b466fd605b..b43392b5111a 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/snapshot.rs @@ -1,6 +1,6 @@ use zk_evm_1_5_0::vm_state::VmLocalState; -use crate::vm_latest::bootloader_state::BootloaderStateSnapshot; +use crate::vm_latest::bootloader::BootloaderStateSnapshot; /// A snapshot of the VM that holds enough information to /// rollback the VM to some historical state. diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_latest/types/transaction_data.rs similarity index 100% rename from core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs rename to core/lib/multivm/src/versions/vm_latest/types/transaction_data.rs diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs b/core/lib/multivm/src/versions/vm_latest/types/vm_state.rs similarity index 97% rename from core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs rename to core/lib/multivm/src/versions/vm_latest/types/vm_state.rs index 63f06f4fd846..55b504a85f7c 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/vm_state.rs @@ -20,7 +20,7 @@ use crate::{ }, utils::bytecode::bytes_to_be_words, vm_latest::{ - bootloader_state::BootloaderState, + bootloader::BootloaderState, constants::BOOTLOADER_HEAP_PAGE, old_vm::{ event_sink::InMemoryEventSink, @@ -31,7 +31,6 @@ use crate::{ }, }, oracles::storage::StorageOracle, - types::l1_batch::bootloader_initial_memory, utils::l2_blocks::{assert_next_block, load_last_l2_block}, }, }; @@ -106,7 +105,7 @@ pub(crate) fn new_vm_state( Timestamp(0), ); - let bootloader_initial_memory = bootloader_initial_memory(l1_batch_env); + let bootloader_initial_memory = BootloaderState::initial_memory(l1_batch_env); memory.populate_page( BOOTLOADER_HEAP_PAGE as usize, bootloader_initial_memory.clone(), diff --git a/core/lib/multivm/src/versions/vm_latest/utils/logs.rs b/core/lib/multivm/src/versions/vm_latest/utils/logs.rs index dfa23685dcda..9e4c61ab059c 100644 --- a/core/lib/multivm/src/versions/vm_latest/utils/logs.rs +++ b/core/lib/multivm/src/versions/vm_latest/utils/logs.rs @@ -6,7 +6,7 @@ use crate::{ interface::{storage::WriteStorage, L1BatchEnv, VmEvent}, vm_latest::{ old_vm::{events::merge_events, history_recorder::HistoryMode}, - types::internals::ZkSyncVmState, + types::ZkSyncVmState, }, }; diff --git a/core/lib/multivm/src/versions/vm_latest/utils/mod.rs b/core/lib/multivm/src/versions/vm_latest/utils/mod.rs index 97483633bc54..04821d411f84 100644 --- a/core/lib/multivm/src/versions/vm_latest/utils/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/utils/mod.rs @@ -6,6 +6,7 @@ pub mod fee; pub mod l2_blocks; pub(crate) mod logs; pub mod overhead; +pub(crate) mod refund; pub mod transaction_encoding; pub const fn heap_page_from_base(base: MemoryPage) -> MemoryPage { diff --git a/core/lib/multivm/src/versions/vm_fast/refund.rs b/core/lib/multivm/src/versions/vm_latest/utils/refund.rs similarity index 100% rename from core/lib/multivm/src/versions/vm_fast/refund.rs rename to core/lib/multivm/src/versions/vm_latest/utils/refund.rs diff --git a/core/lib/multivm/src/versions/vm_latest/utils/transaction_encoding.rs b/core/lib/multivm/src/versions/vm_latest/utils/transaction_encoding.rs index ed532f89dbc6..515f436df89c 100644 --- a/core/lib/multivm/src/versions/vm_latest/utils/transaction_encoding.rs +++ b/core/lib/multivm/src/versions/vm_latest/utils/transaction_encoding.rs @@ -1,6 +1,6 @@ use zksync_types::Transaction; -use crate::vm_latest::types::internals::TransactionData; +use crate::vm_latest::types::TransactionData; /// Extension for transactions, specific for VM. Required for bypassing the orphan rule pub trait TransactionVmExt { diff --git a/core/lib/multivm/src/versions/vm_latest/vm.rs b/core/lib/multivm/src/versions/vm_latest/vm.rs index 3914bfca17a2..9eb342a6c8d6 100644 --- a/core/lib/multivm/src/versions/vm_latest/vm.rs +++ b/core/lib/multivm/src/versions/vm_latest/vm.rs @@ -21,10 +21,10 @@ use crate::{ }, utils::{bytecode::be_words_to_bytes, events::extract_l2tol1logs_from_l1_messenger}, vm_latest::{ - bootloader_state::BootloaderState, + bootloader::BootloaderState, old_vm::{events::merge_events, history_recorder::HistoryEnabled}, tracers::{dispatcher::TracerDispatcher, PubdataTracer}, - types::internals::{new_vm_state, VmSnapshot, ZkSyncVmState}, + types::{new_vm_state, VmSnapshot, ZkSyncVmState}, }, HistoryMode, }; From 6cc9b9e405b03a7e30f3c92735b7452099c165d0 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:11:23 +0200 Subject: [PATCH 03/52] fix: eth aggregator restriction (#3490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ fixes eth aggregator restrictions ## 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/node/eth_sender/src/aggregator.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/node/eth_sender/src/aggregator.rs b/core/node/eth_sender/src/aggregator.rs index 33b9500b2d18..69e0e45a9b0d 100644 --- a/core/node/eth_sender/src/aggregator.rs +++ b/core/node/eth_sender/src/aggregator.rs @@ -69,8 +69,7 @@ impl OperationSkippingRestrictions { ) -> bool { if let Some(reason) = reason { tracing::info!( - "Skipping sending commit operation of type {} for batches {}-{} \ - since {}", + "Skipping sending operation of type {} for batches {}-{} since {}", agg_op.get_action_type(), agg_op.l1_batch_range().start(), agg_op.l1_batch_range().end(), @@ -95,13 +94,13 @@ impl OperationSkippingRestrictions { fn filter_prove_op(&self, prove_op: Option) -> Option { let op = AggregatedOperation::PublishProofOnchain(prove_op?); - self.check_for_continuation(&op, self.commit_restriction) + self.check_for_continuation(&op, self.prove_restriction) .then_some(op) } fn filter_execute_op(&self, execute_op: Option) -> Option { let op = AggregatedOperation::Execute(execute_op?); - self.check_for_continuation(&op, self.commit_restriction) + self.check_for_continuation(&op, self.execute_restriction) .then_some(op) } } From cbf2c31e353fd7a5167fcca7e2df87026050c21a Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Fri, 17 Jan 2025 20:41:27 +1100 Subject: [PATCH 04/52] fix(en): make EN use main node's fee input (#3489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Alternative to https://github.com/matter-labs/zksync-era/pull/3487 Before this PR EN based its gas pricing solely on main node's fee params which are based on immediate market conditions and not representative of the open batch's fee input. This is a problem on environments with long-running batches. This PR makes EN fetch main node's batch fee input along with fee params and report fetched batch fee input from its fee input provider. ## Why ❔ ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] 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`. --- .../src/l1_gas_price/main_node_fetcher.rs | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs b/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs index 259a5e3e3fed..587142ae499b 100644 --- a/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs +++ b/core/node/fee_model/src/l1_gas_price/main_node_fetcher.rs @@ -3,8 +3,9 @@ use std::{ time::Duration, }; +use async_trait::async_trait; use tokio::sync::watch::Receiver; -use zksync_types::fee_model::FeeParams; +use zksync_types::fee_model::{BatchFeeInput, FeeParams}; use zksync_web3_decl::{ client::{DynClient, L2}, error::ClientRpcContext, @@ -15,8 +16,9 @@ use crate::BatchFeeModelInputProvider; const SLEEP_INTERVAL: Duration = Duration::from_secs(5); -/// This structure maintains the known L1 gas price by periodically querying +/// This structure maintains the known fee params/input by periodically querying /// the main node. +/// /// It is required since the main node doesn't only observe the current L1 gas price, /// but also applies adjustments to it in order to smooth out the spikes. /// The same algorithm cannot be consistently replicated on the external node side, @@ -24,28 +26,40 @@ const SLEEP_INTERVAL: Duration = Duration::from_secs(5); #[derive(Debug)] pub struct MainNodeFeeParamsFetcher { client: Box>, - main_node_fee_params: RwLock, + main_node_fee_state: RwLock<(FeeParams, BatchFeeInput)>, } impl MainNodeFeeParamsFetcher { pub fn new(client: Box>) -> Self { + let fee_params = FeeParams::sensible_v1_default(); + let fee_input = fee_params.scale(1.0, 1.0); Self { client: client.for_component("fee_params_fetcher"), - main_node_fee_params: RwLock::new(FeeParams::sensible_v1_default()), + main_node_fee_state: RwLock::new((fee_params, fee_input)), } } pub async fn run(self: Arc, mut stop_receiver: Receiver) -> anyhow::Result<()> { while !*stop_receiver.borrow_and_update() { - let fetch_result = self - .client - .get_fee_params() - .rpc_context("get_fee_params") - .await; - let main_node_fee_params = match fetch_result { - Ok(price) => price, + // We query fee params and fee input together to minimize the potential for them to be + // out of sync. They can still be fetched out of sync in rare circumstances but nothing + // in the system *directly* relies on `BatchFeeModelInputProvider::get_fee_model_params` + // except for `zks_getFeeParams`. Which is likely fine because EN is essentially + // mimicking how it observed the call to main node. + let (params_result, input_result) = tokio::join!( + self.client.get_fee_params().rpc_context("get_fee_params"), + self.client + .get_batch_fee_input() + .rpc_context("get_batch_fee_input") + ); + let fee_state_result = + params_result.and_then(|params| input_result.map(|input| (params, input))); + let main_node_fee_state = match fee_state_result { + Ok((fee_params, fee_input)) => { + (fee_params, BatchFeeInput::PubdataIndependent(fee_input)) + } Err(err) => { - tracing::warn!("Unable to get the gas price: {}", err); + tracing::warn!("Unable to get main node's fee params/input: {}", err); // A delay to avoid spamming the main node with requests. if tokio::time::timeout(SLEEP_INTERVAL, stop_receiver.changed()) .await @@ -56,7 +70,7 @@ impl MainNodeFeeParamsFetcher { continue; } }; - *self.main_node_fee_params.write().unwrap() = main_node_fee_params; + *self.main_node_fee_state.write().unwrap() = main_node_fee_state; if tokio::time::timeout(SLEEP_INTERVAL, stop_receiver.changed()) .await @@ -71,8 +85,18 @@ impl MainNodeFeeParamsFetcher { } } +#[async_trait] impl BatchFeeModelInputProvider for MainNodeFeeParamsFetcher { + async fn get_batch_fee_input_scaled( + &self, + // EN's scale factors are ignored as we have already fetched scaled fee input from main node + _l1_gas_price_scale_factor: f64, + _l1_pubdata_price_scale_factor: f64, + ) -> anyhow::Result { + Ok(self.main_node_fee_state.read().unwrap().1) + } + fn get_fee_model_params(&self) -> FeeParams { - *self.main_node_fee_params.read().unwrap() + self.main_node_fee_state.read().unwrap().0 } } From b50c973111afa660458abbff53c33d524a7d8a84 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:10:33 +0200 Subject: [PATCH 05/52] ci: Update run_loadtest_from_github_actions (#3488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ make script work after Cargo.toml was moved ## 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`. --- bin/run_loadtest_from_github_actions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/run_loadtest_from_github_actions b/bin/run_loadtest_from_github_actions index 149988d63d8f..9222673051a4 100755 --- a/bin/run_loadtest_from_github_actions +++ b/bin/run_loadtest_from_github_actions @@ -19,4 +19,4 @@ export CONTRACT_EXECUTION_PARAMS_RECURSIVE_CALLS=${execution_params[5]} export CONTRACT_EXECUTION_PARAMS_DEPLOYS=${execution_params[6]} # Run the test -cargo run --bin loadnext +cd core && cargo run --bin loadnext From cb5c9b73c39ca2ce55984f5689f565823b2e0ca7 Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Fri, 17 Jan 2025 10:34:51 +0000 Subject: [PATCH 06/52] ci: fix jobs after core workspace refactoring (#3492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Fix CI jobs after core workspace refactoring: * [x] [Run VM benchmarks](https://github.com/matter-labs/zksync-era/actions/runs/12826132552/job/35765453916?pr=3492) * [x] [Protobuf compatibility](https://github.com/matter-labs/zksync-era/actions/runs/12826132555/job/35765453747?pr=3492) ## 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`. --- .github/workflows/protobuf.yaml | 6 +++++- .github/workflows/vm-perf-comparison.yml | 2 +- .github/workflows/vm-perf-to-prometheus.yml | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/protobuf.yaml b/.github/workflows/protobuf.yaml index 0d7f43f1c2a2..ba667430dd04 100644 --- a/.github/workflows/protobuf.yaml +++ b/.github/workflows/protobuf.yaml @@ -46,14 +46,16 @@ jobs: run: git checkout $(git merge-base $BASE $HEAD) --recurse-submodules working-directory: ./before + - name: compile before run: cargo check --manifest-path ./core/Cargo.toml --all-targets working-directory: ./before + - name: build before.binpb run: > perl -ne 'print "$1\n" if /PROTOBUF_DESCRIPTOR="(.*)"/' `find ./before/core/target/debug/build/*/output` - | xargs cat > ./before/.binpb + | xargs cat > ./before.binpb # after - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 @@ -61,9 +63,11 @@ jobs: ref: ${{ env.HEAD }} path: after submodules: recursive + - name: compile after run: cargo check --manifest-path ./core/Cargo.toml --all-targets working-directory: ./after + - name: build after.binpb run: > perl -ne 'print "$1\n" if /PROTOBUF_DESCRIPTOR="(.*)"/' diff --git a/.github/workflows/vm-perf-comparison.yml b/.github/workflows/vm-perf-comparison.yml index 59e44023243c..223404fa0b4f 100644 --- a/.github/workflows/vm-perf-comparison.yml +++ b/.github/workflows/vm-perf-comparison.yml @@ -69,7 +69,7 @@ jobs: run: | ci_run zkstackup -g --local ci_run zkstack dev contracts - ci_run cargo bench --package vm-benchmark --bench instructions -- --verbose + ci_run cargo bench --manifest-path ./core/Cargo.toml --package vm-benchmark --bench instructions -- --verbose ci_run cargo bench --manifest-path ./core/Cargo.toml \ --package vm-benchmark --bench instructions -- --print > instructions.log 2>/dev/null diff --git a/.github/workflows/vm-perf-to-prometheus.yml b/.github/workflows/vm-perf-to-prometheus.yml index 93d33116794f..0868e0902342 100644 --- a/.github/workflows/vm-perf-to-prometheus.yml +++ b/.github/workflows/vm-perf-to-prometheus.yml @@ -45,8 +45,8 @@ jobs: - name: run benchmarks run: | - ci_run cargo bench --package vm-benchmark --bench oneshot + ci_run cargo bench --manifest-path ./core/Cargo.toml --package vm-benchmark --bench oneshot # Run only benches with 1,000 transactions per batch to not spend too much time - ci_run cargo bench --package vm-benchmark --bench batch '/1000$' - ci_run cargo bench --package vm-benchmark --bench instructions -- --verbose - ci_run cargo bench --package vm-benchmark --bench instructions -- --print + ci_run cargo bench --manifest-path ./core/Cargo.toml --package vm-benchmark --bench batch '/1000$' + ci_run cargo bench --manifest-path ./core/Cargo.toml --package vm-benchmark --bench instructions -- --verbose + ci_run cargo bench --manifest-path ./core/Cargo.toml --package vm-benchmark --bench instructions -- --print From e30488e3c393a00449634d9ff39a04bfa38504a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Grze=C5=9Bkiewicz?= Date: Fri, 17 Jan 2025 12:32:58 +0100 Subject: [PATCH 07/52] fix(ci): Fixing broken release please script after cargo files changed location (#3494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Fixing broken release please script after cargo files changed location --- .github/workflows/release-please-cargo-lock.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-please-cargo-lock.yml b/.github/workflows/release-please-cargo-lock.yml index 8c8036dfa47a..3b6e615e2960 100644 --- a/.github/workflows/release-please-cargo-lock.yml +++ b/.github/workflows/release-please-cargo-lock.yml @@ -40,7 +40,7 @@ jobs: - name: Cargo check if: steps.condition.outputs.skip_steps != 'true' - run: ci_run cargo check + run: ci_run cargo check --manifest-path core/Cargo.toml - name: Push changes if: steps.condition.outputs.skip_steps != 'true' @@ -50,6 +50,6 @@ jobs: git config --global user.email "zksync-era-bot@users.noreply.github.com" git config --global user.name "zksync-era-bot" git remote set-url origin 'https://${{ secrets.RELEASE_TOKEN }}@github.com/matter-labs/zksync-era.git' - git add ./Cargo.lock + git add core/Cargo.lock git commit -m "Update Cargo.lock" git push From 7463636197b329769db1e745dd693fe737dbfecc Mon Sep 17 00:00:00 2001 From: zksync-era-bot <147085853+zksync-era-bot@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:19:41 +0400 Subject: [PATCH 08/52] chore(main): release core 26.0.0 (#3407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :robot: I have created a release *beep* *boop* --- ## [26.0.0](https://github.com/matter-labs/zksync-era/compare/core-v25.4.0...core-v26.0.0) (2025-01-17) ### ⚠ BREAKING CHANGES * **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) ### Features * Adapt server for new EVM bytecode hash encoding ([#3396](https://github.com/matter-labs/zksync-era/issues/3396)) ([5a1e6d2](https://github.com/matter-labs/zksync-era/commit/5a1e6d2445d4d4310fc1e54ccd44dc4254e5bcbc)) * Add logging & metrics for mempool ([#3447](https://github.com/matter-labs/zksync-era/issues/3447)) ([64d861d](https://github.com/matter-labs/zksync-era/commit/64d861d1e1d2d46339938ee3174c58cdc3f348c3)) * **api_server:** report gas price based on open batch ([#2868](https://github.com/matter-labs/zksync-era/issues/2868)) ([f30aca0](https://github.com/matter-labs/zksync-era/commit/f30aca00962aa34c8a7acd6e4116290a2b214dcb)) * **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) ([f06cb79](https://github.com/matter-labs/zksync-era/commit/f06cb79883bf320f50089099e0abeb95eaace470)) * da_dispatcher refactoring ([#3409](https://github.com/matter-labs/zksync-era/issues/3409)) ([591cd86](https://github.com/matter-labs/zksync-era/commit/591cd86a1a1e6e4214d3cec74b4c601356060203)) * **en:** make documentation more chain agnostic ([#3376](https://github.com/matter-labs/zksync-era/issues/3376)) ([361243f](https://github.com/matter-labs/zksync-era/commit/361243f3f15e01cf1f3e49b73a579cb962cf0124)) * **eth-sender:** make base fee grow at least as fast as priority fee ([#3386](https://github.com/matter-labs/zksync-era/issues/3386)) ([78af2bf](https://github.com/matter-labs/zksync-era/commit/78af2bf786bb4f7a639fef9fd169594101818b79)) * **eth-watch:** Change protocol upgrade schema ([#3435](https://github.com/matter-labs/zksync-era/issues/3435)) ([2c778fd](https://github.com/matter-labs/zksync-era/commit/2c778fdd3fcd1e774bcb945f14a640ccf4227a2f)) * Features for an easier upgrade ([#3422](https://github.com/matter-labs/zksync-era/issues/3422)) ([3037ee6](https://github.com/matter-labs/zksync-era/commit/3037ee6aa976744a09882b5830d6242ad8336717)) * FFLONK support for compressor ([#3359](https://github.com/matter-labs/zksync-era/issues/3359)) ([1a297be](https://github.com/matter-labs/zksync-era/commit/1a297bedd226c56fc2ba02dc54d79129a271a1eb)) * pubdata type changes from sync-layer-stable ([#3425](https://github.com/matter-labs/zksync-era/issues/3425)) ([f09087b](https://github.com/matter-labs/zksync-era/commit/f09087bab397778976af42c321cbba93f9706b5a)) ### Bug Fixes * **api:** Propagate fallback errors in traces ([#3469](https://github.com/matter-labs/zksync-era/issues/3469)) ([84e3e31](https://github.com/matter-labs/zksync-era/commit/84e3e312688e3aaffe81828471d276e24432d496)) * **en:** make EN use main node's fee input ([#3489](https://github.com/matter-labs/zksync-era/issues/3489)) ([cbf2c31](https://github.com/matter-labs/zksync-era/commit/cbf2c31e353fd7a5167fcca7e2df87026050c21a)) * eth aggregator restriction ([#3490](https://github.com/matter-labs/zksync-era/issues/3490)) ([6cc9b9e](https://github.com/matter-labs/zksync-era/commit/6cc9b9e405b03a7e30f3c92735b7452099c165d0)) ### Performance Improvements * **eth-sender:** optimize sql query ([#3437](https://github.com/matter-labs/zksync-era/issues/3437)) ([0731f60](https://github.com/matter-labs/zksync-era/commit/0731f607a72d18decd1ff74139f190c253d807ef)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: Tomasz Grześkiewicz Co-authored-by: zksync-era-bot --- .github/release-please/manifest.json | 2 +- core/CHANGELOG.md | 33 ++++++++++++++++++++++++++++ core/Cargo.lock | 2 +- core/bin/external_node/Cargo.toml | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index c43a992917e1..782c7a30086c 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,5 +1,5 @@ { - "core": "25.4.0", + "core": "26.0.0", "prover": "17.1.1", "zkstack_cli": "0.1.2" } diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 12d1169f84a3..d0491b179035 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## [26.0.0](https://github.com/matter-labs/zksync-era/compare/core-v25.4.0...core-v26.0.0) (2025-01-17) + + +### ⚠ BREAKING CHANGES + +* **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) + +### Features + +* Adapt server for new EVM bytecode hash encoding ([#3396](https://github.com/matter-labs/zksync-era/issues/3396)) ([5a1e6d2](https://github.com/matter-labs/zksync-era/commit/5a1e6d2445d4d4310fc1e54ccd44dc4254e5bcbc)) +* Add logging & metrics for mempool ([#3447](https://github.com/matter-labs/zksync-era/issues/3447)) ([64d861d](https://github.com/matter-labs/zksync-era/commit/64d861d1e1d2d46339938ee3174c58cdc3f348c3)) +* **api_server:** report gas price based on open batch ([#2868](https://github.com/matter-labs/zksync-era/issues/2868)) ([f30aca0](https://github.com/matter-labs/zksync-era/commit/f30aca00962aa34c8a7acd6e4116290a2b214dcb)) +* **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) ([f06cb79](https://github.com/matter-labs/zksync-era/commit/f06cb79883bf320f50089099e0abeb95eaace470)) +* da_dispatcher refactoring ([#3409](https://github.com/matter-labs/zksync-era/issues/3409)) ([591cd86](https://github.com/matter-labs/zksync-era/commit/591cd86a1a1e6e4214d3cec74b4c601356060203)) +* **en:** make documentation more chain agnostic ([#3376](https://github.com/matter-labs/zksync-era/issues/3376)) ([361243f](https://github.com/matter-labs/zksync-era/commit/361243f3f15e01cf1f3e49b73a579cb962cf0124)) +* **eth-sender:** make base fee grow at least as fast as priority fee ([#3386](https://github.com/matter-labs/zksync-era/issues/3386)) ([78af2bf](https://github.com/matter-labs/zksync-era/commit/78af2bf786bb4f7a639fef9fd169594101818b79)) +* **eth-watch:** Change protocol upgrade schema ([#3435](https://github.com/matter-labs/zksync-era/issues/3435)) ([2c778fd](https://github.com/matter-labs/zksync-era/commit/2c778fdd3fcd1e774bcb945f14a640ccf4227a2f)) +* Features for an easier upgrade ([#3422](https://github.com/matter-labs/zksync-era/issues/3422)) ([3037ee6](https://github.com/matter-labs/zksync-era/commit/3037ee6aa976744a09882b5830d6242ad8336717)) +* FFLONK support for compressor ([#3359](https://github.com/matter-labs/zksync-era/issues/3359)) ([1a297be](https://github.com/matter-labs/zksync-era/commit/1a297bedd226c56fc2ba02dc54d79129a271a1eb)) +* pubdata type changes from sync-layer-stable ([#3425](https://github.com/matter-labs/zksync-era/issues/3425)) ([f09087b](https://github.com/matter-labs/zksync-era/commit/f09087bab397778976af42c321cbba93f9706b5a)) + + +### Bug Fixes + +* **api:** Propagate fallback errors in traces ([#3469](https://github.com/matter-labs/zksync-era/issues/3469)) ([84e3e31](https://github.com/matter-labs/zksync-era/commit/84e3e312688e3aaffe81828471d276e24432d496)) +* **en:** make EN use main node's fee input ([#3489](https://github.com/matter-labs/zksync-era/issues/3489)) ([cbf2c31](https://github.com/matter-labs/zksync-era/commit/cbf2c31e353fd7a5167fcca7e2df87026050c21a)) +* eth aggregator restriction ([#3490](https://github.com/matter-labs/zksync-era/issues/3490)) ([6cc9b9e](https://github.com/matter-labs/zksync-era/commit/6cc9b9e405b03a7e30f3c92735b7452099c165d0)) + + +### Performance Improvements + +* **eth-sender:** optimize sql query ([#3437](https://github.com/matter-labs/zksync-era/issues/3437)) ([0731f60](https://github.com/matter-labs/zksync-era/commit/0731f607a72d18decd1ff74139f190c253d807ef)) + ## [25.4.0](https://github.com/matter-labs/zksync-era/compare/core-v25.3.0...core-v25.4.0) (2024-12-19) diff --git a/core/Cargo.lock b/core/Cargo.lock index 4744b424cea0..0c7b38d2d163 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -11994,7 +11994,7 @@ dependencies = [ [[package]] name = "zksync_external_node" -version = "25.4.0" +version = "26.0.0" dependencies = [ "anyhow", "assert_matches", diff --git a/core/bin/external_node/Cargo.toml b/core/bin/external_node/Cargo.toml index 799108f30723..c8b63a12f70f 100644 --- a/core/bin/external_node/Cargo.toml +++ b/core/bin/external_node/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zksync_external_node" description = "Non-validator ZKsync node" -version = "25.4.0" # x-release-please-version +version = "26.0.0" # x-release-please-version edition.workspace = true authors.workspace = true homepage.workspace = true From 9b121c96bbb2e53be74aa81e0ca250ce9251f8db Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:01:10 +0200 Subject: [PATCH 09/52] perf: optimize get_unsealed_l1_batch_inner (#3491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ optimize get_unsealed_l1_batch_inner ## 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`. --- ...69128b9f77d7ce3b898e44c497dc56a40149.json} | 4 ++-- core/lib/dal/src/blocks_dal.rs | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) rename core/lib/dal/.sqlx/{query-8435ed4ee2a9b962116ecfa522f4ba52c9a0e64d1badc39cc2fef29b1468621a.json => query-970c457cc4513615d9bb6ecd6f1a69128b9f77d7ce3b898e44c497dc56a40149.json} (67%) diff --git a/core/lib/dal/.sqlx/query-8435ed4ee2a9b962116ecfa522f4ba52c9a0e64d1badc39cc2fef29b1468621a.json b/core/lib/dal/.sqlx/query-970c457cc4513615d9bb6ecd6f1a69128b9f77d7ce3b898e44c497dc56a40149.json similarity index 67% rename from core/lib/dal/.sqlx/query-8435ed4ee2a9b962116ecfa522f4ba52c9a0e64d1badc39cc2fef29b1468621a.json rename to core/lib/dal/.sqlx/query-970c457cc4513615d9bb6ecd6f1a69128b9f77d7ce3b898e44c497dc56a40149.json index df856b977026..d0b576bfa688 100644 --- a/core/lib/dal/.sqlx/query-8435ed4ee2a9b962116ecfa522f4ba52c9a0e64d1badc39cc2fef29b1468621a.json +++ b/core/lib/dal/.sqlx/query-970c457cc4513615d9bb6ecd6f1a69128b9f77d7ce3b898e44c497dc56a40149.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n number,\n timestamp,\n protocol_version,\n fee_address,\n l1_gas_price,\n l2_fair_gas_price,\n fair_pubdata_price\n FROM\n l1_batches\n WHERE\n NOT is_sealed\n ", + "query": "\n SELECT\n number,\n timestamp,\n protocol_version,\n fee_address,\n l1_gas_price,\n l2_fair_gas_price,\n fair_pubdata_price\n FROM (\n SELECT\n number,\n timestamp,\n protocol_version,\n fee_address,\n l1_gas_price,\n l2_fair_gas_price,\n fair_pubdata_price,\n is_sealed\n FROM l1_batches\n ORDER BY number DESC\n LIMIT 1\n ) AS u\n WHERE NOT is_sealed\n ", "describe": { "columns": [ { @@ -52,5 +52,5 @@ false ] }, - "hash": "8435ed4ee2a9b962116ecfa522f4ba52c9a0e64d1badc39cc2fef29b1468621a" + "hash": "970c457cc4513615d9bb6ecd6f1a69128b9f77d7ce3b898e44c497dc56a40149" } diff --git a/core/lib/dal/src/blocks_dal.rs b/core/lib/dal/src/blocks_dal.rs index f39a9c582df9..7b2f2e33fe2a 100644 --- a/core/lib/dal/src/blocks_dal.rs +++ b/core/lib/dal/src/blocks_dal.rs @@ -835,10 +835,21 @@ impl BlocksDal<'_, '_> { l1_gas_price, l2_fair_gas_price, fair_pubdata_price - FROM - l1_batches - WHERE - NOT is_sealed + FROM ( + SELECT + number, + timestamp, + protocol_version, + fee_address, + l1_gas_price, + l2_fair_gas_price, + fair_pubdata_price, + is_sealed + FROM l1_batches + ORDER BY number DESC + LIMIT 1 + ) AS u + WHERE NOT is_sealed "#, ) .instrument("get_unsealed_l1_batch") From 4c381a84346f8ab88d3f01dc2848c7fb5f2b788d Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Fri, 17 Jan 2025 18:31:53 +0100 Subject: [PATCH 10/52] fix: fix execute encoding for transactions (#3501) 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/genesis_export.bin | Bin 0 -> 895228 bytes .../src/i_executor/methods/execute_batches.rs | 15 ++++++++++----- core/node/eth_sender/src/eth_tx_aggregator.rs | 16 ++++++++++++---- core/node/eth_sender/src/tester.rs | 3 ++- core/node/eth_sender/src/tests.rs | 1 + 5 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 core/genesis_export.bin diff --git a/core/genesis_export.bin b/core/genesis_export.bin new file mode 100644 index 0000000000000000000000000000000000000000..a65382a51ebb02bfc37ef1c0c31258bd2815e8e9 GIT binary patch literal 895228 zcmeEv2Ygh;_VC;-*-Zi@0YVCdY^Va#2@vXr5Q_9(1W8DNKuAIoAoQhoL_k5B!3q|X zBA|ePph&UM6ciL`7DP~#CgnS4W^UcPyIJzWd;jmf{FJ-X&di)Sedf+BE)W-g4&#(! zYd1`Ny43XU|KzrKvE3cttxu%ye&^)67sCP$N4?QA?k!D2vnH*RbeN#KquY=D{>u3( zUs#`f^=S0m!KUGt{A*{=?fA(jFI~HMT&IyT%Xzc{F&i4bJ+J=uS*MRwJbv)|AM3_N z)?EB`=p6IKp`HGDtK+1IK1u*Oznx(che28I{*3UB8|DYxf2P9pUq%)Wn>c*^)Pc|_ zI%jh41>-}?#3xs%xbe`Yr2MM+wRZ>KtKW7+pP1Vt_GL8~wPM+GmD+rIw_{HHQ%}|? zc}WRV=lAD2dCDxI`1I|izL`BfZ(QI=`cJ@*pd6-}D+suaDgQ z@PTtVu930g#DFfA%AMayA2Q=+^KPXlg`Tg|{N=u{ytTIC=nE^I+o*CLWt=JiIthN$Iqu#Si0c#9Q!LxuU)-qJT|Y{PcKe9wtjT;m(FB3w^8NjjT31; zdc%awX973w?A|`9`VZTmxmWG*{+qq}l(-kdE zte>2#^v7RcPkj1|8ix)g5AAlaW8@}hFqmT~A9n8Ghnws=rLgD=mp(YrD&|VX(r=Bk zC&gd>WZm^6S8r@3-!~_nhdf#Tt-o|yDYNwEuHqwQW%k<<0F5>tef4_9+-k;Cqs*nF z%U-$keWmuB?tilVjBlUH*V1*`AU*>=82I&HsZ)Qg?H@X_Qet%7E&Y7M(;J^zZAiHE z@DJxUsvP0urPl{9XxhZEd(LZ3KZuEL`NZ5clN(x>u5G&Zl_;MfpFPpO<_8N7wXlso z(kObp?Uh4a2R5#;QwiatpP&CV_`9rPY4tmXeZHdH!rTV~+A7QGZ-JP8ShV&1@&n1C zzrWdWW5JJ|zFr@-ZFu!rf3JJEE_B5&2}deV4?Q%m!}<6vZ?~xy6QA0@+uf+Y-WyaZ zVdt08tt0wWofzi?lQ_)Xo7wxcjog{EX?=^&Q~Q7Hvtz~h&)x~2{%6Bp^V|39x9K%y zn}W$8wX91&^<4Qo#~)g|#>}~2zulC&zc*W21MuBv(u`VP|GXxx%AtL}sZnW_s%)*_ z{a5qegpN-Q&)J~tlh57UFr@AZbEh%27riv0?TRO_T=L17Hf+h#k{2o&4=$?qL9K^Q zZB!Y84y_??3Uf{${(SGgyU(q!_D9>cf9Ku0Nm@>Bn3&qI_nN=Ixvv7@oY13Bq@}o& zzOfy<8Q)8tw`hIqL1Rj-e{pxQ6*-@7`LK4AKc8?0qsnoH3Hs>cHjVncIoC2P3+(ID$&2r_2}7OS6{WH^wti}ZFtVv zoA*w>b3jKPsMp=8J;(zf;V)yBe9?93gn8yp`?|HAdZK3U`~R4}jf_~?sNRvzmp-bn z^HAyMx86MZ+IqutpRM}g>b%OYJHb(B7_MEeQrSFiYw+&m-F1&${;nv8DU=ZW{7_hkirv3=2IyX++DlzHhb* z+ZO%F^exIZ1=G9gaVp!>-vY4!O6xXD%bS%|eAnmm8;&m(^?YWJ1z(RJ+45l3x4+zZ zs=iYjLhYp)yGIYY{?v(nW4|3aCG*3k-~S>sOds$?&cQA%E{y-cxs57ESJtU&c%N{l zdE)Tdx%TrOLEkm1GqI|1;8UM|vTx!K7y6EzN*-1(xxD4|F2B9%p&McI;LN{kCH5Hf z{tpAD&bNH)ce2)RB|WrJ6|cJexqp1$TTL!q+53EtX~rwUw&zk*6=*`eM%$V%xIJv^ zjoBNzjjf)rCm{3Kv3dhwa!=oQ;cov@em;wTZGOk}ZuJhQYyLCi$nGroN2DJrlNHRT~o^L!9!w%h1t*4nO}H)jCbu)~d>YHTx=T zt+Hu`$an+s6)ikeDZUw!1yi46Df871v z`IyGry7W2l^1$t@=go>9cO*u5;lZEZ>$EW*n18-rv=H$I;ato2S{vExEMG zm9GPHf49uW*@XOjGwHW8#kw7fyAwHUS2J^eQ~s3k&i#0mQ@vm|lnUmT-T$ilX7pLd z@>9FKs`kfBb;jGAr(3FUd?+1sMD)rj+Y07f;+u^tW zxBFjRdVY4qlPkXX>2yL&&>NHMo!WWKKd;x~IeEuAy{2Pd(w2tzHryESeY+EDbYQA{ z89m#_Yu^!UMdyB0IXZlzDi`$V^ZmM2hd$MNUfrAxr9Qa*x{y(+Mbg%ve-Fz0`i+no z{sR+Lyr?IWx8+BD`_I*=MnMaNvTuGIy5Y~jn|D;bIww>x^=vffCZ6OH+Dl}|_AYH& zWE~|RkIa4QgQ*icu1x(su6O>Y%g(&tx#=t&m_{$xUA3kFPY2%)<7@A%sGK=0?$De4 z4}6-RIpK{1Im@59^6bmSbLMs5`dn}mZ5%;`3xKJkCn&!UiCog__2`)0hF3d2jOzPp za^Cej6P`Gida1{;)xY<4o|`I1kKfS_i8_08SFNV?JKD9WW>2aoMsn%v`$7s;j8&Ru(zqg^v-?2(p0$CbD-I`z}m<4*TD_0fbk20m5%!pZx6 zUU?zS=h!EGMh0nPnFWC9C;@u2Y0{}~?T0tWdw9Fb9dNzS3&9rE_tSBEXu zvqR?J$Y^nP$CK~Qd9&()Ro2NlJ&Mg6w=u0>sU>HZ{ZZVv^AvqdVBBU$;`3|no^dXx zv=q{(XPc85TQ1u}S3MJ*+(zFf?RUc?C>8zWoj{Ddz$jV3X5me(Eu=MPZ z@g=_gQsB@V{C8rbc1t=Y_O7rq zvtre4D?P>Qe05-Ym^)As#I&;aKb^mP(1mGBUtSu2xc-Ea*mD1Uh(GY=o5w@YyPm4+onDF!P_g= zeq-v;LNx-KaQJyCKSdB=hpb%oY{rxbF++Y*nzu|nIKA2YuLdJ_-~XC&YYw zq+X?H|2K~PvZun;gbQmr{&d54^xIz#TUEQVo~#qQbn*1G-(hubryghbc4eO${rY=# zM@%esY2UV-<43RWc@M_IVGZG$zZZ&_@B72DMN7MTrioDx?%Z{BfFHgFystJ?CegG5m7ml?xe1 ztAA)}bwtr#@2B%PJ=?U-ZPbu;${oh(V-77XIcMy-(n%$EOuhHy`q$pA*)nR*3meP! zd2zxw{d>Nw27%&K^=Ekm`W(`yRnIsT6m>$6KFyo^pHHrGEO1xdypN}Sez@xw#lCx3 zHDuCzd71kj1jJjL>c}z`&}3DLCQ#P7kHb2*QRV3FAE|0MC%*EuH=!pZoWZDaRI#AH zYWDjo`Amzu1Jb4kw(QaK)jrRzZmhRPvA2Iqopz<(*oMnOoG#?Q0g+sCPR;^!y{an(i-EEXb*bZrn#Lmvq_)AeY?kT|B}#ht_rK zNR^>ub5OnMcHdeB!8xJpZ#q{~^nDuo zzlcbSe81EAai2{%c4x_r-|7eGj!w?h%04J@7$156h{4r^sY74#AfV46XyoPIn!_A@ z#?RRD*#@KExAhBKv+}L$dH2tbKHRKY3G;?lZED=Bf8`NkYd#0Q^taWH2-$8Mu)Fo@ z?E^1VJ9+xAE^BH`Ui(z<<*h!uzFj$Lo!`3au2WB$)yR|Xc!U^>*5^|8M~2Le%~EC1 zgu}1YTO;Dmol7n`{p#(Eh;2jmx3^E9(R*C+OR+Z&K4W*>vN(0T=HM4e6Hg!-n0peeCa+ zYBRCKq`@~YJ^lL^%PLAk)BoY;oQ?Ep4nDu8X8&{T4p;c={LH2c8|)eC7;x?ViodRU zq0Hx7J<25=m?|Bo$GhkkSDO7}=EdAQSM#fr8SlUJd6$&zw~q99b58l8#hQ22foY6r zF=WG~@g;KC^lojPQDxzk8P7MWa`@(-cb@$|zxE>|HW&bDYj_(>%G6PNEXg(cc%KvCf7Dzy4>u?F|||>q=ZcKQkL|?gFR0i{;=-j z8=J^GA>U>;UG(OsZ@neFJ89CJON&Q7abR<+ZQcKoKuJFwIy5U4;t!oQpyf}z?RWV1 zdgVtnQPrjis*AlmKc|AC)2DjwH_P=N-z%5Q4E^o$vfYuj``ucxI~f{zxzC`6sTu)G zfPdue848Da)}CQM-yc!ym*-Y)PmlTCc+0=|EW?5^AC$IF>3%&c?m(JOKZ-1&gd)Lz zdK#z04CD0jEGMp!<);>N$G&jrqgB;=tetF6xqa+ry`*xr|LW9a#|N)=-|svQRgT9s zQq{%;Y(zq%fE|+?E{p7+d}f64?9H2hzFM;8p0d;W<}R*n8-MzJr#7B+90*r9?&0OR zU`n44o7FeodU@p5KJQ)sVf}NR^R8b@xR$)*V#|vS&OM>SC#sR@%^huqe|A69XzK8h zAAR%N;@9dtxY{80{GS2mX8ihY)~fK&bo7lHrk)JRJRMd|SY6@N+?cs1q84{J)BE}J zb&ns9(dpdfLTldnQsb^L!pYLeZ z_SqAAM|wXGiqEJPryM_z?-sT1^sr(#^Cqlo{%xSuFJi(M18aNR3* zZZUFNg{!}9KAQg9tNkZG-K;iQe($TXZN7Xpe$Yw@zWxtgdwzKNJ)z_JtsJ=gnFDK= zg!USc{LZo?U60Nyaec#20k;n}&DRIzcHRA637O+}@o2@L_kQsD5B>KGw>Cca`Kk5Y zs;=o8mh>=fdyK1dR_@MSxW^}P6bIaRxd-Sl{;|R6_`Um+9bOpu=9F!HV+P)CIKs9t z=8w(W&Sz(3U0N5VKX)nD_z1S5$|fk_IRRLAQ_XMxm|U^bOPe~Dag6M&GucQ&_|Inj(Px$nOM*SYN?3BKSOq_IkN3Eqxw%s~zT(%{9+_;$?O?}GM zaqi0D=cQdIf`D}|{_DEMrv^;uQDxu$jsB+Hjca_lXa5k>o+?{1#Y&I()ZKqSk~VwL zx5JNwKlO!q-cy5jKNGU%jb~5JE%*7zkDolab@tfW9{SS5r{ALHAtz_=f9SENJ-xu@duzg>XPn!pa`eXOud3m7Lib#{U(-G3E7=}iAG6 zMq<|1OC@{UY<#PlnXhwya}d38&YRIHbNk1&KG}UYD6RXzSH3k|SerZ~V&{o|E!w1v zd$*TP3>v}makkWb_sS<_^43`o4|RUM-SYBVi+4_Kyzx-a3Vq`{@9j?p4N9792#^M* z|HI?DJ2!Kbghp;{ih`Y+aE9zANAK*biawp&QhDIz`DKT(sPUYZn`s$F~=<*86WX{ve;EQ@rV3$c#_E~ zn?Y^|zg$MdJ;HR^lEHU4^O5IxvTA5LiHCaeBb|E6tRa1}6SJ~Xl5O$1LsN8=s199F zK5=`NNZEnTZ|Awg{*jmGesy4Idvf?~v9}71`21=}gmuh<#1E7Hxcl*JQ}jPy-WR^z zuy5eB=nQQOo-T}?Tnu#F4}(s9_rRRLp}8aIdV}Q+PDaH}oiwJ~1EZ<>t)=~)`=q+q z%UF*JiViM3#u@yek(Y6Chw-Bk@u!~{7rCWvn6Pv3+Fvavb1t3rU%IsYm&UBumelUA z-Rp50m!4c|m~?VN??bB(?T=60G3k6c9UeA$)4Yja-9EkU^RBA*BIQ}Tl_6l; zc)JENWccMYZis)Xbcde40w2QdIaa-WSXHmi31jm!uHKtq-Rr)?gEa5$d0EwLoYx5Cr-%3S zy+8A;J9gDLJS84QCO7mXll5$C4+C*&e_}h>+g@V+@O;+H|)b@oH3F81<$ zLe#Z@!YQMNz&*1*s-dkE_=|``A(Kz9gYp~n?`^7%?1D29cEn6lO`}M-D=NrV79dStw!sC0X zP~G7dCv>lSEIZ_fy36)nF$CLAT!|C4B`VCUb}K-y%w z4e9CN24#r1v2s;*ss^Ko8LHptMtw_Tb?O9`Tch`9hmb+)xN4xIVXIbvvBV2-?4FSqsq~nJIW>R z?z2#V)U`j&x9&E$Ui$e}F9yti>r~23;mQ8THY{1RE9%VLP5XXd)xhBC4kEp|n_s9o zqvZDav#wXIk}x`C>8ry^`Olm)+2?)`Oh z@LNl&HC{gM>Xwv6Lg~nZqd#3=BVy@?M_=%GK16RX+_@jGa_Z^D1iZP6#;G@OAe{0v z{*j)(0-h+;p1$6lIG_r=JdcBG!u1!hc@U z8l_LSv2v$=)EOS|;+drhcV zYd*mA@_iaLOf{-#3eQM*dAEwg@1wruYh-oqHT6iHVK2Yi%5bfIy~?4B*3^2VL$`(I z!%N2(*XakdB%8sh9GGPL&|P>W* zep+9}aBktEhim4IsQ&J$5nr9j{qyL_;I=tEt~DIr>q{q?8h+RFpJ47@o}qB~J#sAl zd!g;aZHq(o#<8YWIpW)GRrZ<>y4dFZvGC1lm%rVaTkprp`>X!GdBd8%s|P9lCqrr_C%i!JQL;JJB}bc zqbpcnzutb{`{$-)ooF9x9k|5*r!sSE+zuXIvE6~iVTta~+vxcS8d}nsM<9&T%lp{i zozPM<A3r;H&KDC_HqNeFY`MAnX94YXYy#G^xF4r84%NEr?WMyw z-M;&xN1ww#iVIt{tJ%6@afgluCQaCHHDo;9E3fIr@1g@XZ|{HW3+HiQj!d6=FdsNh z**g97*598T|LU8EpNwnn->OuT`YZ2r@_RaBnz8$a^PXVpy?a&IJ0G0;^hBRj$9YSi z=9}FsR4jFVdcW=q7W=0J4JfmxON)C`-@JNo{H2##HkweprSmvcIk1GN+-n}xOXi3km(~ew|cK@!WZmiSc_wI$UA9V8@wYW{&@2^d2-n_-`r88eVc)Rnr z)Amdl8Eaqe`rVdd-wfV(e$xm&`qYd|n{4Rm-4<;Fem>dD_i|NGbkAhF{sn z5l4?4J2>#Q=C>|q7>>X6Va%78K5OsWx10{GAWWGzRXt!rSN^GLc|4P^s!bEruJiIN zrV5JAJk<2LV0h(~pKM6nvargJZ=bVY7!rc4S?zP{kdp9Y=Ky3INA)H z-Ks=VTE!B-JXP;u^oX5j?c+LJeCac?vig*K)EM~3B({N+mr@DFKtmR&y#!+&VHx$b z>l%AOsX%PPB0~Ht60)~BYibI{Cx#s;9DzhY(m~V2l1Ku{DnbG~6EX$s=Nqve=GUS5 z186=D!$8~ZG0<`fumk)H{Ul;LK?o#sq1|915XJ+4CL^KmSsM-b+}G6*DBvEfV6c%G zEGLx+wkIY-K9pMq8|FOR~B@5(eef6y+(Jay&O4qbN^Rms=)!fDd>q4178x*q`u&eWt|bFso362T!>t$6_UTxr3G?SjLxPy+r5{>%qC;DcD~=t>^DlZyKxT|1Ph$ z$B6x5bd9gC$w>R1PV5vOPy&T_&XNtQVOD2g8o!;ei`PdX2OY5uUJIDZ?(PYBFYmmVOW;;mR4asECOKbF}H-^F@z ze4Bj|a6JjagLN-9ShMc0al8{N8&??b-%k54%5FcQMVl>PxO^d~^+N z5T97u-e?9(5vyBFY`rag5OUVlGnx`MC z*Hc_C1C&QW`w|y892g(MFLC@obX}j9)}>(U3U_Hye2VM(vef?ZuIsvjtt+l49WT6} z%H$if!}fLM{PiQ}U$_rZSxyNA*X67@wH04CFtWVuo8pkioXNv@$!8A_5pW!`5yi9Krr8M zyp~r0FYf$qKqCoL_|;ze-6R;=Q#r@h13easAHhHpVu&BXj0pJG!s8vUf))onLpJ)a zxZ6j|Ew8Ea!JFmzudDI{r2IEvJt-V2H|03PIy&FKUB(%x-yS5F>(5s%*Txw3M zR4YRGaA$6j!pHYu3p4)ph<{imw3}MOzhaz3v_C0+AiokXr}Kfd7&pUn`$$j8yqt~) z($jew$jq(%`ld3T7Ic-z(*P*9^N5k@g9QCJTCqJ`?&IBCoUW<8sI~`WdV2@z#RmQ9 zf!^eDeR`A2we+^Z1AL4GS5x%wC47u8D)l|#euNQhiO!{xHI6~uudK5T76sQmShDHW5z1vk~aRz;-j+M)2Ac3TuY}LJ-{!dTx9WKm`4J$1Ee^* zo#_=4!0-m;lzuD;1{P;eFxc4o2_>2T2m2J@*E!YOqpoM!#N|jJ>NABO%;W7r_c(tL zpkwemi3RH+-2nYZ5PuL;7#}A_@NtFakjB3V;L}v@PZJ1eqd)95pD;Qn#Y8&sp>bdm z%+qE%4_U8n2D+nq(!%1!Tfh$m`^K`xLh@rNH;*PgaBx-{-+-r&9mWNGNc|O=55@jC zJ$s-_BjNTIr}Lmk=R?M4|2O%Nz^+>$#<_|^I1%}f6lZv}yf3Z`#iwNNfE;-O?b_l% zqrlH-KVjSrT2p%nf+#c|g>iHr$T!2&3{TjYFb2|h4Ezjr1duE6SCYc1z~pq*K#*?` zuc;bnB>6D}?II$X|*b`7vM;+a^vez7q5@AHc&9}JAo`vM&t#APwCeayZ957wi^G;XKw zgmU0Zk&k_@uzpHF{6s*y3FZ73$i`9PFqTX5M7BrnQ+!Brnzsu@c_*xd_14OLIuC_j z(8s?7&!So1!pkqSp8WDhL{a*b067L%CEMv{E9;S zm3mC_>m}ebSVw=bBWS;(o$&vQ{K9=|u!~*DFVs6#aXl3B3*+a&9RHJ7=to%oUGfT! zTTnaS4Dw?*tcK*b3k3Kj7;@OYGv3<+>kCQTe@4B;^6{*%oR4~m=35@1yute5pT)ty z++S;W6XY4lH>?KmQQt5)@NnzDJyiK`e@gogqTO{@l&jqCD#7RFN9eptJfwUljSu>b zLirqEXZ&b|{@vQag|$=J{3N_t1=`+5 zs5eQ1neJQr!ag46n_wR*5%nho@Ngf_*9N91tv}Yn_VKLC`95AdA3kk11hs|`P%3{ z9$Lsue(eLgj#b#VTKkCk#gA-1TJ|G+#2=4eiSZUw0pnTXek?s7Caos`rSU9j9t7zm zi_wqrT$s5mw=X}T^StI#4zYR4<@$0+F4xK-EuOS^MEwz?z_YeJlbZ}rHkik594{t0 zo@ks4QX<|&`@2#B@wi8$R~TPFCp<3b0)GK*MEa$AOTxG17>-XdAMJd#_|V6vOjjCw zSdM#uUr4`+(Ds1m6Cig$&KI93#=Tg41n@%rNB^-fP2_(`uY-J=Pw+er#52J89L)AR ztPsuse8_rAiw`zlxm+I~a=8W{V*H2raeW^ECPg*hdk`)Qfb)_#9?H*Fq%(Kpp!=PQ zae6xrMyC=TSaF|M(H{w?a=#2%PIcf+fV00v_o)ieKaX>fGF-n9p&s==VO$`;rEyV= zfSp(TBawdD`BzL&X&!Wq$Dt&=S-zq3xZ+YiFh5Z)*TKiUV_GsYEQT_~R!q5l|#KdxzSMf==c92e#T`+2gzu0KAxTmv8R0rq(r7@5*u zm+8cl+$$u0Jj%U7%2jc{gUY=XFiQwCpg#hCYI!Hx)neQwnDbj85EiamIF&ySL%yc+ z2Qnl$G#+HW$@tDP2HM3Ucv6bm<7Essl1A}M;~PK^=(mV;TAFfjO>mZ>kG0KdKC8#p z1xL#47EkLy?S6%R(x(HLbn0;(3c1cC1o21p&-DW7AINNLV@ zWdz1|Y`qYlQKTF2J?`%*HEB&+@eg_)LDw{p;gPF4v6T3ir$vshkt(Aspxd*98^`^nfJy ztZ47VVm`Ak9q7lS-y_rzsaJu+y8djhjw9a`pjW^%8GFTH+qPT=h69_ z%Xv0G8$8tKgi1jUm;dktzs~n*ZbwLXBNT7GaQ+A66s-<&RI)$*?e=jY=}$aQ^$%k}w0F4yo0x2vRbZeN7Ew5tm1=P|pAt~Ut- zJ#~`XRia&^wyS`9z`ytast3c?73$405I?{tA$dqGL1%%?Dg7Ao5FTKU>S1Iw;78D! zzz0}gJYOL0YD(B z4*gp`M1F&M;y$Vmw3Fq6AC!pv5XbCo3J2q+O8pE~HZeU;iFCGc_Acj_q z&i^gvLdp;u_#1F0L~|}g*27vlEu_6|Vdp|z!!N9y=5{*BJ9XUD&f})s-$QwbcILnM zT*%)}`xCcCf1B=4NcM!~V-NCx;X$?^_4R;UuGIsc;+TcRvr`C>~wY6Nq2%f8}z0Jj&%7JTm(n#zE-*7ruYX&%+zo zd!Dg`?%Nmtjm66>pE3Uq^az!!z79YMo_n-IKFB}ce3ox{2y**bgiC0=rubd1H`#d| z$TqTl4jb$jpxgw_hkB+2s7ut3mVM05^4$Xpn3e|%@IER0nR@d0dtIz=+0E*s8gjeO zb_nKy{BTJBoRsA-%zFiayD~xV$omLr-=V$(B-3@Hb`QL#EX4_6nyb#AgTTJPzWM+7oDwC&Ib|8Y+_Y_%!hM-x>W_VOg-e#7j_v z)dRYRYJA^GfPE#PlaknwM9}u^oCU@?05?IKB|4$|QbGc)kL%`KAL+0Xug~nf5IC7+ zLp-7YfPN9%G#+gIjM1s(D-U>8`@zKGC0;E@ixxhT-+z=pQ%HYZlxqc!_p|xScCQ!@ z6ZhLgLB1g!(>RWwA^at3cXPfa#ZW@fcn7l|ME)oSCB&En@<7XHlrI$avX&kS8}BHr z9rHD%H(77!k6$j=jvx0&Jnw(`0!C5p{l)!Az!s(fU*CA>pPeIkxIDYfV4ON)X0 z);~AK)=R4If`8?5ty~o2b|`N^zF1%3@~#-Cs}e#TjEm#+?I_oXDT>R%O6(pG7Oxfk zHS24veZUrILFFNpOR_)b%i;O~+%$v-cMPB${F2Kx(XS-1PXhS?@cvCKldGkvTqTBz z>=GnLz%#5aj1SjEv^QnGVDB&NmFSS!hfe1h89n-f2YBgUP8oaydLnf!?a*f=wTtxV|Ui{pFHZCmsMIs((e^9%1 z0naI1;^!35z|*5I;{N@m5+JuIUGeqz{f4c-Wvn=E?$?Op7sqQW;P`&#<165M5tgwq z4~8#x-_STQEiii&?}wuDipe<~uYi1^et#P6QmO}eybk?pkXQdr@mAnF(1X6yI6sSY z?-Xx^_W(Ws+=%v==IwJql}5V?V8Ol$oL5xTlk&}jRryevojbtu z28#Mf?-Y);2j&AFHT#+9w$nJ32YjmcF-wwy+B<#~n4IGKnD?Z0*2)*?AM8iDTwfo{ zF105b#o7WswBZIIsyF^Y3I`u7tc-_dxO=6(Y7msq~~egf2|EMI*;0rDgB z4|S?RU{X z6!p1iFDG+*nT`kAO)sGRth%2-qIaY-f!_BC@CtO_=Pt&d)_R)xc;b7New6yB^y$rg zqd_oWln>VW9_SgyQyBSfZSZfJANPZu{3euhX!l`UCIal1apL&6U5f8nDax&liWWZv z?c$iA2d?CwwJ{r?H3r5N%f}rJ>(+$j1KF`S5!x;DAsz;M!+UXEn`z2HtZ}hR(C2y9-X(qg=w*Fx z>0i5Ecf6aA*ZhY5l!E@PPZV)~=pQNjt69YTMQZx1Uc~)rI7Z!(Gyn#R~WPchuw9F{t{_q^4 za(+{axIgNz$#UQNWD)m={-0ug)@nuEAI7_s{naSq{xp1TnOVgB;d>>D`B`Qaaeo@U zZJG0U`?FRm;{MQoRN`}P5%-7wtg^p(Zu>($K=0MJ6tNz#qP^`#&sd{0<&Rqra68;u z!7aWDFaJ^BDc9#YxBZ3D4kPIKDB8c79unxIf%yQu6!XMckiuet)^`PxJ#&&QSSc zMSCh%!5>=r0`v~?zJl`)-^VT}|5(cukbj10_y@k{;f{Zb!+Tp8_riIq_$Q3<5A2S_ zv-?Yo{60`Ki<=R1G{2W5n%z@kGO_zh;2ifH1ZQf%@{J8xJ!21cj|-_w&#Pn;7Ej9- z&tVJnzDi3D#Ishw7T&)qo}^v(-`%b|vFxSm{D%iUBA4sSd8s_0E0aH#Pbl2qUBX3s zs9KO*t`Ao(*XSqyey!B6(>YUi4~bMR-V0Yy`m=mqK>GVoLw}_`(BD=U^jF%w{IvzpPD@S%b=KgjSaerF5_2pyk@B1R|56@*P^nexPgt0*FF7_)${s{!hrKk6-<<)#{ zInUsU_Nmmqs(yPYSKBF`_)QyU%rDsZ1CAF_|IfScztH@q^>g3#u0Lyp2EUs1!A)_t z55nRURwF!_-N?@EN1E#3`>B?Z<~l46Vd#o+hyXZworr#s0QW2)J@I=W;ra%C|D(@L z9*3xceiL5HwwL))CYrYK_C(o zLO1R|vGe5F?0lsqo8PCIAW;A8U+zDN@ju)LjRiEg;G;s*fj0j4n|J+L%ekimkA59H z&n?^S`u2icuCYf7J%6p9rG97;;#(UJzv3Ofy_RcQz4KiW_lNeSQV$?~#TFqxHS}vm z{1#<@T73Re#QkaZ_bk5_aevzV=hH>pp9Y^+ln1dzNH1FcTT#UQY3S3kq=@^|?(Z!v z;{LSz!OI_We=k1f{#HKb{$6;@{Vglv{&2resUPrMQmh+(H$XX@^MB-Xlp1+vjVE< zN#`Y>)7S13v3bem`ty>@HS@A~f{%KoTEX9QAmLWFKg8ctD{%ZPOKoqvibDs%*<|KN z@%>!lW2O5^F7|T+w=+Kq+X?vo85-6|k8J;4#)}rOEZ!%Vdy7|3@C&rMOTMLXI62j?%-wRk^x+vg2zt?o}kBTnBM7rSnvhuvNbOGe!&3hOUKk$113OetC z?$3HV?;w!Odm*mC`9Yp{v2eq9JgXSTXo`4qspn<>pf47Uc zKeVHO7sdB+tQ8+`f0pY-+@E&+ekkJp(2h{x({i7ielP(22D+^cSlbuht%; z<583scE0Use+^yX7x6>F%E;yV{mJE8{30VD{_ONUPdMZX0D-Q6%;-5dYkTY;)0Cd@ zjZgX>Jm8z{FBO!&eDM8QiN1(@AFQzs{cg4m_IuzxRe;Lq7e26NM{6b0Y^V3D+DvwO ze}Q=JhxoAXy~Ag7>8T7dl%Joq%;Ni2ZE>HfqcJ9yImF-(uOz>Sq z__Oq3`%#uYY=6qfhwWRLyRv;NpWZCr+?(%jjpF-OJ?TBzXm3Q~qVRhT08wbyK@Pot zAs9TqL`);a!uSZ@w*>eP27aRXd_4pkT@R?h?#;1wP^|;DM*IV^pg&wM@&51DP}CCA zp4LUYkBYw6EUk+;Zg6J#d+Yd~Zx&!Bf!_D(L!T=tcs3u{7iRN;rb;^0(3!O(j$gqS zTE0P)g(=GQ=~$LC-omH*Uj>IxN&+h&U)aoe->UOHHh2%~TzHAX2J)#n-hRpX0^c8p zvn#%Knh$i2bPuJ3RHXD_qwV~)^`RbxXYGV|qTlCae8%Ak=L8%_acIZ+lfT~$`>%}t zc{-BO4SbKDz7GxG`=;kmOJ8C4Cm9HZ<6CezV4uT#)kb2l@p;hu>&n0kVv&wHo($}M zV?W4Y^a#g_fuHa?0rnkh0;dy^pIJV!%m#jk`*TX);rBWbf!%|r`JQeWXhZuEzqbn? z2E%nR*y(uS7G)9d{ND8-UajeqcRuJgwmt7oZmehx%CLd*ow~dr%2}0UzUaQ+u1koG=IQTM8m?0^*sN zDgJ=}?CidCpwC#$H=68-S35tyoXo!ODAzNw?@=a*`}1&K`6b5B435Cc_ek&a*A-rp z6>)YF%HuJt$Czm(o7|D%3p@GK7qp^B#OXYX~YNk>kd4jCT%}hY|rG{BkvPv?e%VmC+J1jUZ>+B$QRZ+ydH24 zevepX=Rz8%>CVno--`tFoed;{)-=-hBH-7C(){q83e|tuuZSmssPM#EVmZfeX*eN@ z>yAlxdXVc2c(Df!KE7ux(*rmz zqC6B_$Sw7Efl*IH;XHgbaEPn+9`u7B$SFD=st*9LF;1li_;-O(572&zP%u4!_h?eM z|021szLyfl>1w_%|1$dS@jv?hkECz$JEhESA*Ba$Jq>0bsSEZT z$W`HS%E5mBqm%zgI^lk;CyUc8%Zkxzn5<9MW~+@yiVi?1ASi*D$wu31cOaQ^_$Hk`cVzg zu9EUW@3DPjxZfSl$-v@ zm{Z7qHdYS=d}XF@`8am4aada!yVCQ(k*2N~UojK#KLKv(7ubBwXaYeR3_%`eae6Ut6b}$eI*uCk{ zzk|~iem@V>3t77KFKoR%-Xki?gGz+O4ZZcl=zXS=UkXAW;#0lgU$q~KSY>gk-JA}& zyutcKa$ns4Z}+L8-;8|E_meGmARP;7eg6#3iy7=FhpbN(?7f5FJL0+&?7f5F@=U&9 zJG3jT1NeB*4+QyWInUDK`zlBP%6)XJ5&V+_Fn(Si#2ND&BEjzgxn~&(_WW`v6@)tY zUPN#kaeQF-A|A}*d*Z##DCa@qCiD1mX)3?#@$o@B9N%N_zm+6^>GxKGd+_m_jdkdI z@$fzfi*NJqh7eOFCRa_Bn4AT_4D^ja3^o?uHdbW$hKj77p%s_E?0#>KKT9@?!&tI; z{0R9Fq7RUF+PhlQ@$gDf!>D_d{*Q)Ngf!P9i;yr zQgA-!H)yBFZ^0W_KE$W!y)D+>>Tx;In^>=p$9hc8^>ts*+E=hE<&CyKYajRJFnX2z z^7=g#+~+{K#pSoB_XD;4S)W$VugKQ%w8VFGUoN<($als#W;dGnzMQM~$wawj;_s6g z9)#1-TcRg@_2bdESTLk=% zxeK|C2fhMsb$KCJ$E6R2E4;1B^cpb#~sXb&_OsHNeGa0%A zy=hs6{o;7|ehI{TsNO~S1p8MIpA6L7Ka$Rq(EGGQPDpX~1kumt{n!q4p>2o6~*PkO?gAVjxD|UfRbfrQFAW_XFy%_Kdz^ zly=^K9PS5)yw3Q_V1sxK9f#S>?a5#Uhoxa(oW^lNAf_{g(h1)$F^l?;G@$UdFnAzN z8qoF}j|My2|AkfO=UD*TVTf*?j%4G4c~W}@3Mf2@4#5{f{ukjI614CH4v)$yu)~?$ zf_PLQJ;y^xG#%f^6b|k)(0y!5_qgsLN09H~_&w8yX5fF^$7v1YqWk0KXdExd7m9yN z0^h&3^ElGKWPjUQ#`dA@IF}M5JRMkPAp(MuB~I4A5Z@ zqj=EW=MDkilD0$q;d(#ncLg->!&y!PK4W1V`u-QvA6+-A73(S1S8x5RDa!TnB-4*( zU5J_ENf2xlFFu6E^Fq3b@d1gR+ zA_V+#@N9fERh6&%J*Y3NhZjSU(c>AF+Lu)c&#hSD{t;I9#nCt_S*87N-Bh#c|_464(RQ zRzN2pi^INUbYe}C)`P}rts{B)Wll%U1RIqPb_09zbqzZL%SiEO)caAuxa9(+24uo<7XqAiaewfcLzJKki@P2P^?Ep?w&i0DfpZDZGZr zN8Hb_8L6KG0OKfrdAslkJ|2hY-+*QU{D*xc;)nF4=~=vAKVWUN%Mwg>idQ_r4)yWc zV1`f8e}R1PpTb9q`G!h3AHXMDKM9|JckEAuPYf(xkNX6OUooy9$<`126&fF=ex(|} z^=N+*e%ZNMkPjzSAIL!Aih8MJ}Sv*F(ZRIf0oM`3yhP=J8$L9%OV{YES?j(Mk8-FJk2KkSmOCEeGhlpLaPfHgCCH ze_V38c3jlo1gaACx(Vz7ln;cxhXlHk#tp(_m^`w4W`uQsW2y;+?FR~whB99VPuqDq zpQSAijdZ^d05bk+%jwE2?vEO|eIdy=`W@=R_el@Id_ezL4ypMS+EKo=9ESO?AjHA+`*O~AzK9UQ;>Z}! zL%WCbp*n71IpSfx#PhxIeLdJmMYsSbTwWh<*AF3}JgUX;%IWwym-K>uZnbc^T)#iL zTuU#ky*yrhI+n||?fLq^s97A6r8z!fT&Xl(!tny@^bN)50+;iF_Y`10a=HF|V;6c?|Rp{PCFLB{&{MyhK5_`goG*Kp#(Xxn^C6c+Ol9Y!olP+%Fg-+C_}dVKj`6 zB>VqgblwQ>O+k4h?&qc>9YR_^jsyjTT!-?S{!)s#zmn?yEc1)FKW*H^!}mTPnVz-x zGt&E@ig4XD^RfE&ei#xIaz2$vU)% z`_s-(#J>wU^2l_rxkuAF_%Y9`@?-9=N)h*`y*KxN-5BMx3{}Qyrz~6;axqACgNVjw!p==xG=UUE! zNQ%X5!uF+osGpAX%+i&ZeJj$1%lHq(f$*HL1NS3h%J~Du;ls5FXSkh3&-mUmO@u!um#-CtW8W z(0h>fDGq7S9}v$_oD+mXwtkv(6QbV5Qo65F_Dx2QEZ$en;z7>J<@)O^muuy$R!(c_ z7t3Q5bfjr-rQf5M^&~$B<3r~G@4-98*I0kD-Jm~Sxm?p9wPe6{D!bDKzq0k>_6y)G zmEvFGR~Bz9=i?He2AFR(MY;a?V--iUZ$@kWTxEAJsGd*j~{Z^ZE^;*APA)6!vK<*1qC3G@lZ)5`&c zOwp6~ls@UG;M^3@54F=_TKi8+^ zEt=n&)%Ugevwp?;ukw7ml>Zd1KOOFUqH>xvpy_0znBI(Ov(2~&%VC1_JbbwyV0btm zn~CO={l2t*IT%YKp_(9nm5b+ah;b>aN2byIt4tmV z=Zp+L{5~#F4AWRWe!f{choC+WUG6TUM>r2%!1ns*p(EhENt~y_=F8#u`m+6V-#Kg? zEDh(O7vVlx1nkUALA(%tUcqcy#Pl3HzZ_wg=1b2*8|)?yS3E~xn$F<5KL;IAMgu1S z6_(B=Y$o zg7+c7VEB6jf`I#{Aeu*c-H*L_|Ep^%n?pR?hTS!~lu^wQ`cmFGabod{LBZ zD(+zCFHL|Tn~x%l#fb(p`2p+OLLD_ zxx@b!{l(-dtQ$LD9FgE*UF358b&<=pcoOxzNw85nlb@q2cUI(IZl_{Azy?^Nb__g$ zZi?jn{6B$2V*$F0I5(wZ;Il`LcUNZqo^@&w_oIpH{qMXl)BBISZW?@Arxo$~Y4&?8 z|JCP+8>Y*DgS7QEY3shR`I;?clrgr!iMdHbo7i|IrmmGO3jQ0AcFtXG zD1V@bSa)rdWyVTj`O38Rw=uk{+j3ID?$EYTr7PGIJ~`G#n0c#Wv&EApZaQOQbnJq1cQYyeqVld)? zZj;k8hVTlCmQFuC$^=adz4}ghQvWzg4FF6^ZGjpFBv?SOu!Xz z%!k#K&;oR28=jJ#k&#8K68ZmKG; z>!G}!hw}O^%G)O9*s@ZxZCRrRQ$7L)8k3!wLC@>s0Hpc3Xy57l#i4Yd1^=g#vTB!% z^s%=1_FZc>s$0*Nl$c=~m6L*9)r{{Fi}ftxV!C)j)4I{oF}AG4?8Fh8v19wxG42_F zvz09w_)2ezDY--A64T)|DHne(hQ+e~XuHQl`8oIH?TCkRTaT37ToiAp89Uaq4NlKY z8ZIeE7_-kuU%`FNUj4nQtnI9S)x9voPuW8K8Yj)=7wB(fJu8hXIW~U{kG~v~T zB<6I58mvUjRDgiCxtU3s>9$c>L$VW-Q*60;uuQqO)U@mzlv3?td$b$WrA?b2@jVr- zV&vtUA;uqMiZvWik|7LZYyT+#dTimbTKgDcmnX;_agO!`tGcz|a z9oV#E+K4pgy4fisN2O(_BzGB|l07sfFE6PP;@6o**eCNsNdc49^jXp^+e3~g;q z+A#m`k+9q4?$0fL?&tWY4s+L<5Lo|KDXE#+DP6NukcYLSdb#p7hy!``*IHQ{Yza3pR8&Z>Ya5!F<64BpW@V*hPy+%853Ep;`b7{vKXrrmrCk4Ir+`k+K+&3| zYbS-eZc0mXauc&DgDVAz|eB-adf#K+^Hl%G4w1BoN-QgVS8}PU-InbUt1E0sNks zT!87-(ZP?Lb$!ep4&u&BZpw5Cjd?QShVjAaH}H5=D60a*zNEv_v^15 znv8h)SBSDg!G43)C;#;T?zfEqqD~wFx(E2bu3Uz{dRta}rq1!0QpsqgeubVo*6jp+ zrDO~NldNOCj+sNsc>t%QzwF8;qldYseXQ3@R;y035z9N)Z32{wYupwr#TGVLXq6($ zNlXQ;lib9Xm5HH&w8Zr8w6=&QSLG1Mp?^uk;B{3ue_)qo=A?mVXah?O79i7>o~cwx zneNGlnq3**gxmjsSbU6($M|S0$$}nT(n2~M~l$evJ+fK|x z`9mVZ`#IO<{8$P0fanu^7|H{^$qnkPfM&brsD$7@@GzO^K7Qi2ds zg?5eJNT8hA7rhhH(?AW2ewz}8tKU~HzJl+vx&6yY9G%iLuN{~WDLKd~qP7R0Z#4Eu z|AB5}Tea7Pla@mVrdx)YFn-jJFg^VV>9FDM89X&PRf47WqtA=o z@9E`>d2;_u%8B{nv(BX>yTsoGe5&YEhL4trt;bjhv5c_AWoG22+xnTk9(Gw1ft?6ws2 zQ^xR&%rP10_T`Sva^~KSu|0YYYSTSFeo&8&U3z)|rYQ#d#^xgaD9Pv?_u(s&nB!JF zl0^662a@GMfl2Wv9MnOJ~dDTR8^KM^F=69IS3lr5pM+% zJ*MWBz+_Jv= zX}{J}SDu*lfq!<7;bGlocGQKtF$vlti!3~o!p0T{P`ivH=whq7de zOv{!jL*iW40N~5PO-efI3bLo5`^$ZNPPd6F^9NPRDM19MELFyR_xkzay@x%-_<*eF zRhZfl@O7XnLz7S`D@_?DGk!c{)5(^yva(ZZ@~&m3luF(Iu+=#7Q?UCg1nan~0{cJm z%xy`TBeK#{ltx~9qI>(n9qgCqhyU)b+&H(=PG5HwpbYD{t3o&4-Bbv0Q-(!u7uQ4E z^EW~-jPzi?TRgxpdMH)JeJ znw#lhxpVoWEcEdE@5?LNx~(#`MM4}b1*E;J%8Na^;4Ia_GL?$X06Iy5!Lng z`a8F;THMprCvsc<>Y;nfgu_{@?{0t8aDP2^M<4vT2e}7Tgz&j#?tzV}vF6M8S(sRN(kD^QRAgqTt4SD}SP* z!ZaL;r%!+WEXJScs9`xebofNYgHKd2_zdDt4Co7xCZ zg45>g{X2O5i@uN5OghiazT0+=y;o*(5cXaftlHY<0Cx&cKMNgWdMZ!9%hS_%8tyIL z3*~U@9fN;`!-xB@j8tgBdworz{Y;p{DyRqLQCOb{W$60~cyAWar)ieD+<@oD=Y!^k zxY-n}5A6ijj-Qi*a{(P#KJDKo(D}o^^n1I44PeH>|MWd7_^j;T?7SMZ@WFNnH&KKK z*finyV3t8CEG@hNfqzkAJqjPXGXeG;aQ}xmF3dNbfF5IMeR@v_;pgFjB52xll9dPD zg#PP5{pp0^`69>T44xM8n5@BLYXiq4+}FtPNYBqpcr*c2Tz6t_gZKnIc2(lpkigFC zWBph`4kTp{$7BDhpI|E7%CZge-`$=JYuonxbS{Wx{eFr<`cl9 zX@PH7hEKocLQ9ID<#fG}6~eIGO6;*TzYog?)}i@k`h9QH0+LVXL9FIIkPm-WS`X&I z#%pei^#M{h{vAU4{X(EKyK??;c7fsrzAxAn@dW*9$2W(MZ;p%cjbi!0%5;1dVuNuj z#%IwTpAp6Z^lN}VqmWMC6BwO1I9SJS{}A`7=2OD^BMRJ=6CtgtH>Og8V=?t2mW+{-7xW4WW=mxdK`TIfJhWi1! zZ3NfT&yYailmf^v)jC5il~d9>RubhQ?GN=S_7_MsYE4L?{B|qf3|zj!x-&UX?^opb zK@9WnKL8wDmyLk$SjfxgdX?e-|FQQTa8?y(|M;A8`!-Y*Vvw_fqQAQ~*uiz14OVvC9z*AfxS3Rct@7fXx{Nur|0LM(4gtVv8nP4xGD%A9-e zxqEk4g30^;f8Ogy&d!`E&ph+AdFGipX&k3+Vz|@Lb9uZ6!dVX9DEw`PI--x2-^~h5 zhx)$Dp%dQ5zf|;&uiq~#(77PU4A3{@3nla^@{!b+85q$+2_5h~%0vhJU0DA#e0h`j zd9YVtxp!Aa(j(>ko`1KTlQUsb&jotpM4swBxzsPgfq{m2Vm_I_`VCS3b8v&nT+AoZ zOKAQ=#g9qN;&Xj#*zc*Ko#A+I67roaTuDiC#o5N{-^}IGZ#E%(luJn(=LBJ^fZ**p z!K=6)+T#8OX9_sqW&F#t1^ynQNzbQxB+FX~9+PvVy&7%rp`5f=^QxXZJfGWxY~l7S zT(EznzH=Sd_cD5JFPoP0e%X2Qy{_y${q8HzyJIqQ<$HwAMVfz+lyk0=duz=AK2NFq z==@3hAqqYgMho1s3amG=C5KzGfA z576Bxdh{%B{CkDp3;bOI(Ahh{F>!^$-LP2u3Bf;%XZ`+f0Te*?+QlmG??;;KJt82? z3k;`*_1h^8IwrbmNbr3CpevSpQ1dN~4Z(59uBAz_#?T&a6#UNR8HY4p?AJNpoHGV-gEUkKqk4S z(7pZ6GTLLh$9Z71?_6T(Ds`gJcUtvECMoH2(?Xw_1xlYMaXrwu&}T}&=P2!ECPwY? zJ=y-^6x%gsWx9pr3hbWgM0frJsdl-i!JNqSr}uB0m9U3qQ#t=Yrqi!X3+qepeJ~K& zjS1H8-gSVT=D8!ya)eGK3q7XO<#r$Vk?p>0i`Wsl1!`9<5d4DB(Jt&lYxf-@`6Ac+ zIg#DBQ0OLO?LL+lB>x2u?nZ*IgyJuqQ2U@zZucc-@%lo~>-uwChRdImovZL|qVR1Z zaA3X|KFq)H%b{`j)b3OGTniuT`4af7J{LW9tMI9Rj@Va)B=`b+;{0C6gUbIi4e@S_o`3~cw_IW;yw}x3j z^Y0EZFVj=*{ckus!n4>bHEzJ)Ig0Kb7Wskl9^uFNE0@n9&3fNFC+&lO+X+7Zw3OI0 zq0=sO=?cA`W*9&IY2Du^Z0!Uq2jX^-KU?LL-V;)Qm|^}U`BK!63?|!MH6GiExWA!^ z;dnvuKx=)>hkKjy(-cj&Djsr<-UH#T1$xRjkKsMdPsVBT@^~86`$GgzpktOV{+wik z&{^srrL%+L^rUpg>r9qEN`D2=AJ!L)Kg^rkg?#+@`*0DR(S7P0Q?@gtR6kg`Mdccq zjuHg7!M*VT%fC5EthicDW zfg%L&>pHI{rRzdjpZXVweDvoDUdi5LKDkQL?%#F3M+<+N^R#{6eorjXEO^Gf`8yh- zcfQxb*YBTMd^@4inbCDZ;Dmh*Awh+X@F@TcWtlU9C_c8l;^{wXNmeI|JK??;;L z2OPX7`Y$B&Vn1M>M<9*wvj=#B-t$~8?FM2$NIAeI`M%}{k`MbK&+^vQ{042;<~wdc z?V83EkJDeSdUa%4?T2K6`8t!#Gyi4K-b9oqzXsLA_ngBLxJvAYLd;&y(|j5?DfrBw zKJ#;Oyy}0p8{By+mrP_gsGZ!i-QfOI=RK3scEJZ*5uCl+4?KU>?FYS&VWq3z-*y_+ zeh5U5!B8BCUgSP-;1T734-a5CL{I-|d-RQno9?p7rOd` zl_&otdlY-YOhmqv(dmi);|28z1>S=t}hWw=k$PL&X z{-zOqio7yuSq~J_1(A1P9Fi*y@H>iJDTusFwTrxya*%hD?`wV_`H**FU%Hy#pzYdx zC)G~vn#PREx6E7~*U0Q#>epYM?vQb&XRsW`r(wh%fC{5}PS(mh?mxn_V>)8;t|0PG z!4F>{6V6Zr$ulJ4Fg;6bJ>VLHyp_qB8#UtAw$c-`(V)^V-OsvVk{ zNAu=CfOR`$EPhwW`Y0vuS7O~Xo3fK9s(fn~`IZ#<)|hHw{A>>tkX)N_&@c8Zo5Qo`{}}iADLoJO-r#;4y?Y2O z_&4vMr_1YjZ;Ff;ToLOxsqG~5+DW+*5An!l`gj> zJbI^F`hrD8Y4l0+3oM8q*pu%qVV-q;zRKT4XL|}*W&LaICdjWQqN|9nLZ^xOE>u6F zgK;zcB*g!)G8wAeVt9*k3#3H;2z0zx-esyAJT~iFOT9O z{Ng-rwtq=}vA@87G?HIzm&0zr{Y)rdNVSXnQaW!CI)}a1F7hjx7x^XSAipHv*Ze^8 zA-{z0T+MILc5OcRh54s3ohLj%UWt7+GEPej1Y@ z<7b)h^Z#}_OEml+ov~d1_NvjDkG-ZP%)kE~I!g?S=&Ye!o{1hcp%MOa)ZQ_vDaL@vTi`l-sHEaMeALF89< zf%KEpb!Ls;+gP!GavRqhYw0R$zt_m^73Ei!?@9C*>$q0t3L?L?2cfLmpSC>miE|ly4@vQGg$ho>}PqiD(>tr5Tj&NN_v;&!FddePpPAV{R1vLhK}d z4c&iuO!Hv?bGgxK9~g5arJYdUhky+F)(KM5{~`v(x*sQU zE7c-&4w$}2ax0~B%kEo<9px{^cTag8nK(h^*Fg-om0xKLiQvs+zlPV5<$g`0ax9to zKRHHtCIVbp!>l~(MUMT`^5=g?jwKO-{@iY7sQhk}&d)4SHu}4H__WQg4DSg>O@+o;_L}&SOd6v$z{0fcu zY2e2ed6t%Vj+Ghpn?U}uJcHw#<$X%+F!ht0)a5G2ri&a)k={UKDz}4NyR{w&%Bufb>;(9?q#f}`CE%B6-WjqVjBi*!q!#J?r$i6TO6vE` z*^b2SuwAcdf7z3yd`|r|xt#VV_iMwR)Bf20N}MiyfMMx=zR`XV*GllGBfYl{_dn8U zp!$eEXb^dyoTBipP58vDD19QZW(07hdJKB0e4Y^BQD)brP>=M3`U9Y+8p`H1aMb>d z#gU{GzA!2Cq59SYy3g?-P}vMENgErYZ^!dE`Xe9Xz)$S#yner-QQI+QZ7$d41X9kG z_!ZG-CHh(7`k=o8vX3*DMRFxz``V;j#?OD;kC4jCJV~6W8JQ|bTIo9hzaZf|f%OAy z19CU=IGt7WBP39t+B2z>RKBo3A@Q=MPwPj>s6F1ZKOymg%9jNAh}%V+hV1)ryG6VF z-}NiFN0$2)T(Qd&pbzS|5qbo4lDR?T4dR2F#1Dt>#KR9~LfKygM;uA>ocM2gUaK!U{}qiW}N*r ze?KV~`SsUgdd?%R57DshpcxqLeC8uyWZ_!eoSZP%6eDI0S}VzYkna4SnqbodROxs zv|XF;Wab8-3)7fmdFn6E$@+F=%|fCVe|fe`x2sGp zhNgqo55|>!3@lu3R{F^i{Upa&dZ|(T<{ps#YP0fOS|fB<`+?Sbi0d_5{hhOX#qAaO zD#v=tU##O=SuPG9hA34O60kl8`ywZXp68=$8y zm)Eg&-^l-ImHivUW0(=}uY3d=uSY%cuav|ibG-4Icq4J^>W@g@r+6CdhRpwW#o{&Z zMt`(E7W)BzxEt!c9Vzn$!B2GW;J^i*u$O`|zHm%aOfCd07jRyN=W7jo8RTfxH)x0U zN61bk`S7HjztjoRUwTWS_l)dsnC{~>laK5C4TityLcnu*I+XZi|14@J`8(Tg#*y~2 z+Fr%}z@7{TWWf@W>lUsRw!ZEMdb(d}tX~&jPD34-ZPUy3oZZiKrfB{Y)pIjQF5xG7 z4vq}gmjUcZ;^$Aa|D4}M@%e~*BmE9z2k!~4j1zcyJdKTm^Fxvk*mnwP-r)C8d$-p( zvu2r3n4G|qKL>%4khO?|Q#q3KyO57xS)3V4&^n6N7ZOjE^=(|-E`ncy3v|JDv5nI* z3H#kKZ%v7hOA7tXk+^uo@#RGirN(G{+?Y6gA>hN0_X(MP7;ev=4{8_r=X(k_@5kBr zIM_oC>{kpmjyeE-M{s+*9!jCDR-SkFNEt_REy4@PK;o&*My}|m)Y&R`jwbk0{Udr| zy9gCzU(v2V9<}R_)f4{=J0PTS`lrc!A^ss0eTQ=}usbX4rF^-)1b*hYwTg0G<>h=E z?`h@CJ7}BajLH$hE8|PzK484yKf<5o?~E6Cd8Bb*h~qDe@_Q~JbUj4uCeSVW<3P7e zzu=?Z;v)|fx=1M9MsduRZZ$rV=`aTX95aaEh|zHte6ulSJ3Z5_NhrOdJm_V0z#EkF zS+#gh#YYs+d8Ed3Hpk*Qn}trnSD?p|cuuBc-Is^5(0I<%4BIj8w#Z<5U)_3#;~FaT zPQM$JUIx3&cm7=(uR7ArGhQRTqy91DZXn~b@vx9n#IFb^3uzuI^$zbBkbSy@#uchx z-MHI`-tmp-9if91WF^xtd1!>sLvK9lnX2oc@yzd+X#0b)u3~+YlJ%AGrcyhp>ukGr ze*z4`?N{ip5!~K2k^Epeo9eXno!CBwjsHsN{)63*5P4hNr*K+T-?XZ}nWFke(>0uS1Dp0Z4Y{xK#Fw}(qwx_YDd)jVO^wJK_>(B^OXK*VI5Uz~``MnO zHSmiNK0=4rb52)n1Jju?o6$Pd>40yR^J&+N^3$>WyyhD-mCKQIrgHeM z0H=c}eKDs^l)j$QW<`{~lha*M`VmfdL}}i?!9J30e}8_TkJ7tvI*ig=aypQ-|2X6^ z7*g+*yVG2qXQ$abbAR`ae2HVEtCcX%u=~&3Z`e zp)AHD@?PdMlKdpS>7Yg!%x1mS1}J= z5x)pO*!}Vqm@~GsunxPhWIwrLeFr@YzaH(ELf>Jt6J6~Lo4mkJlfF#h8|*VZ_#GN= zHd5`F>k?vLgdAr^{%<`W;g3#@m(pOP^!)UQ*U+zWU3}_+-rP-j4KUdS;|I+3u$CiavLm z*?#wYo)@?)S5KUv_4^ZllfEET2skoRBRF^+k$F$kf>)A1j935X;9sF*S@-$E=a3tg ze)(KD)=7#t@X1zuZpC9f4b}2kH8X#Vc3DoCB$bCB;6bIEtk&1>oksf~qjf(dE|;0^ zP&e*pweq?s*O^YOzZK`4b-nHKdOO`^@!xBl=Y~9gzliY0=i1WG+WN3ZVP`Iu0`hm75Z`<79MR zGU5l1;+Vx>fpd*^KThCPd!oc&JYV}iiTl^@zM|iSW&V;N)gtzm#-&)hB+M)n$#qkaJogt^y$WW$mYeX<`4vr)VDe z{{2W}ooP&s_)7{kUE;3>Us5~8`1md^m$vcoQV#Sa`B8kl3z1) zdq($1dhuVs7SS8?BMfczU*{GIy(xV+WbOV4^N&eqJ7j-E%7NY_-`D&=@^})6!pEPM&Yn3Z2z`sr8mJJ+(!2rr$>f{bIhdH%flReMwtF zKhSuq+?RwmtjF;6XkJJA*Q3118@bQGp3kNFqfi>f`QDDWUg^^NOm2{Pz=BJ9t|T5% z{YcUM25L7){lmSp1axcHWd`>$@qFCwS$JeUgZoK%JcE!=3O7hGKl0tMMG_cQ@`- z<)aOmkFsvae&7z#-!Pl>ehNL;G&sfa1~_luPVxisx_Ob0nT^zsx)IR>nJYv`C}+_LY&tJ;%RQ6zG_9Yp&i2(G6Zv~hW~lgi(*>S4 zy$8UblPjqFzE$D7Rp0=hGaNYIB=e^CSAh^YA1+4Z!#^#0#bh%IAII}V@PV#~-)rE@ zVfryKIOpI!^jgh%B6oCO97F*GLGELI`F$iOJrLr?cpJJcOkbh;m7?=&i4ACe{fC&| z@DBtq+QDxFn!lQBBKgaBjoJ-$pET5cQ=ID(`pwFH6j9u+mA^T)H>%3t#Y8vI`m!$Z z0Se%*u_wry@~oAXXC1*U+kf^lAI)c#Qmn8m1o_H`ege8 z1^yTt@7~<+Cbr+C?N{Q2jqNw7ewByG<7q>IgXZ&OR9E#jbT72zF z?Shj;ZWR6E`7*vlcw5X4$uoaizm-YtLj5|A`Jv2D*}VzI48cC;xx}y0J$?#Dwzqry zrU`t|kH8n{yMoAXmK#{Xr@9!ABHtqUolS?LU;G)OU-Z0OPe zsh|g8U(HkfGEd-;c`LaOXL=ky)h}$vVP$XOBY;+`<2-aSr@}D zm-Rsh+rdD0KzMJoaXRK)t{2v@y$*Q0%IGIZ+xQb5r~1#sY?qFsgXt-dwEtj^;YSY= zZ_^)X%nyFQ;C}LY-lQ=n^^G}?^ShFQ7n5w|c@H_h3X=waS)Z1lj`=vouge&}ZY}p?GQcOb zXL7%XUd147{4Ey1--G@_oSw+X`bNfMv_Da&dgzOKSx3}9#CqA754hfkO3%git=uh- zhe_LYYfjg#Htwoc*NeG_NN<y%AoyngIHTUc?tzeRjYbYgr*@fP{-xIWv(JRh#f2K z?=YP|oma@0LYaKA^cJQUGG4-pc)Ih%e$3@{{lxpESVyS+m=roUA|L$4$-L^xdx;M5 zlQ`;-&nFVUBze4nl{ddDvmbMZS^lwhTy7HSFYvAMPrbDtzhJr!>Q?A{ewXRwK&3a6 z8!Popj?1O3{aCB^)Z1nDW9}H+FXRLFms9^z`b$Ye?uvaO@-nFZSo__V`yFfhO+T;w zrnz5hPXxB#0~4ioP3|PyuN`mtb?yHheOfuNI5u9kTXXP#H#46kY`=-}ei836g77yj zRo3r0wqI+vrqxc*E$_bH$WCXwH8&&b_dp$QwSF^Y{lYeAWq31+=k%f4pWcfZokP&^ zmfLZNqP64grC+~B=Zo#u+)cJ$v4^0D4wim18W)NEX_;TS7X#<Um{^;VQfbvYZ%g?FQ3K zX}@A0pW(-Ch2}7xFE1hGaF4EY1eJI9=k-Hl zLf|j=x8f2e6xkWHS>R{nhc#94f$<4G1l~$Jbhg-`Hf|#}4#X=m{BxpseS3~?BZj{K z|NXguC4Rp&GFYB`!Sjk+o&;V~a0K7RsGlD6HxTHL?(LQHaV7r#>FV#O@N0z4RL{(1 z{e%00y3h`O9Csi2HKKLG-+APSOdH>&Bcf<>?-E!k?*?_t|7#(h0s- z6!S&<961zl%^<)N-!I73$v#I=pC@|3IC(vdcw1RV8#i{ZYf|yS_ok9usHb-E`$+Vn z*t7neI;$508Go`)$Io&+8ozN*Cj0`tJN5a9K7Y&4oEz|X=-vSyC)OKmuelOGs{85w zEIm)LJ@#8kPP{052>$CRlM|sUbQ?lKjl_8EeN4FUi2DoNK>Qf!5BM-X*2j2)?|A)% zdo1NXrv@!&^Km~D?~59D6tDl?9ODxW=siffU$a>En^xB7y2{;@>9>pNmk=>j_|BF0 zxPMalZkX_0Qs6g6_NlPbO!%mKOXSiw7H=2YbBq#a2ma=DKB5I!U-{8Jrk=xiEO-s^=b=B zPkr3aHQS2*Mg0w^zJC+akbn3IJzKxce2?>UL#2JQy$K19TCCHWpd-kuM^dQIpoBlB6*lPm{Jq$lI|si~ecDe+@O=O=SFiN1`TLtU9weVIC$`Slmj z15fbyP7yq(R8JzG^<-+A@M}it#c(`Q?Olr3_T3I0RD zB06-G&V*0>%KJO}GrSs4;5?QT`!a;RwKH@gz+-tHIFhgH95_-32>)ac(WhOv#_uU$ zJy<(S<`eb>pa1utk@X4M7rJz@9$@|xI|=!RFkH=&4?b=Ye^Ns4MKHzh6LGziwXa*s z=*7Wz!r&_)zwQ#y6R1Vjtw!}>E-CtM2H*yvqFoR3`cK}{KQ6?0-OYV=r{!BR?S$$Taw^AL9uW0`QdY1V$^Jk$0SNkj1GyX537llqG z9xl442yn3;wf6Mp^Ux|%{Uze5QAN<)xX^g*y z+llxS{xyOZLAC*&kn75y^&hj|wD%o`5^siiTfy?ymUFb+%GAe#KX+J}-VC&!F`ElN zx>ByFKQ|SAOf3_p(^+giUX)8|U&j4!+V4i=4T^H< z=Q7W+_^(JVrOy?)g!>a^zQteZshy8|#zZcqXR2Ik(Q$L!SUuJ)PB-&uLdBP*J5wj; z#8;-e%I4u=H_&)F!N0W^f|`K(@t354NJI9bjq^Jd_>b&T(^${tz;Sl}n&SZL*0XvP z6El$M>Yws--`A(rlQN${O_z@ARnqfy>&W~;asNxoyso5&6?*SmF_ z$NRVa*8fo2i~Q1m<#vnxpUV9Yi~9d#S^p*>{?^7?orjdYSB~|49p<8yVVb$Tao7(EIVThBd_>v_q7%00agD1M$w zcsxk(m-?wbZG6bo z7#?|_zYe;b-=2)}*@{F5ka?0Mhod`X9F$G{j=JI3g|0IfuqsA+h9igthbJXHF< z#XpyUX~grL7*2FwJXeAB#f5+q^k)W9`;MIBSTkX~qcNVlBl?qjZBi)8?NpqvScmy$ zVZ=uqS4HD)W%~2S2p>i9`RXT&$LCAj2F?d+e7?W6@R7GQ%YEz@2p#BohG<;T{w(_y z>mQEbU_VK}>ogtmxs@Cv665Xj`3ieJ!u0WcMOyHkUZ8YZVGp?-JddHQ<3ZPA_u}3& z;lFC@?qs{jA$LR8mk(#H-nxUKt#bze@dA77kzGKz#nc z1xLR=!V&q&%HgQrsA~TjKi3N$8vEFP8t+xww`;1$ftCN@K;9R&&(FNlbNmhoa2@qgYnE}xX;l1Go+k* zvC1d$Z{;AzfG>R4R^e4W@3<;QMem`6$gOb}9;@%us_!$cg4g(dZ)7)Z$m2WQ&abn- zl#lGCQL5+H(0n;hX7$f<><5uP!o3FizLDA^uz218Z-_as(ED(|#MY~%Lv5Rq1k@>1d?aj3CceJh*e!{wz z_kZDs;&pBPTQdKEb6F`~fNx#brgdFw*LRSNnAehZZGru2u3gt^xyp5|l#AB2X<4rt zcO%B1^#}KPU7KDe^JHY-Bet%se_!+{AZ2}C|AO$be?9bUGvQ6omHda!nPg@vpH|u* z3}5u!vfkp$s>G+2@?96r4^}qJkMBkDi~Gr~EpSkO4(zP>yknnE@M`HZTK}fI)Q*t( z>TUfy8}wPe{yiV|Mh6)`R-VuFxcHGmys`dZiB^%I4^s;+;}Cw$|$qO}{|Rkd)0@`DETy0PMFQ~sp?4BdGcN#8I`S^T^z5s2Qen?Y0)y_5Q z_?)vq}@@-MK&Tzq+Od{3(2@2MEwrFJ@1$$d#exU9m(HvEbw%{ zG1sx2;Qk=(1fBxHP2(Es$rA(Q@2l4NA`K)p1P36W%Vf8wF9Z1eAgCn z4s5R?$L&Hse(ZmX_WAq#9ams^57Z8l0pTC*hXP+JFZOkMn8G)V^?3;#3SR}>0pJFF z1;Q)X%vbn$y%51SSoqUluKV*NfgkFBx!|Yd9$#zU50(DaJ}!O_Gc2_mK~062eb4bz zXn76w=j*z}9K!9r2Lv-c%6^r<9PHascr)YiIi!ZKEPto? z28nYa1h9X(AIu5SuZcEUw^_ePwfn9l?xyIUO|;3nO~1QjqVGB!z;s>oyNSLH*)NCg zJ+UvELFeZ~d~B@puGwRJb5UTb=gok~He%nbu zrmyuV&L4D$pPXBW%fC?ku9fluen7IP@{xaB{N|z6Ln?nr>y@-0eb3b52~GHr^A&Pe z`=yD|bLfo!$nV@0)2GWco|M+@rSdu7*YDHd-W>K@;5^=3;PD7czj|*CxnP<8EBvav z5nuJg`GO_PSE3*LO^oU{3;icrfC>CkdXIMMVDU$!4i-9xUuZ|nox%Cn*;Fp6_iHEg zzU_$qDXtPkW6Hlv6SCiw8j$tAUH>2h`|id+NB!A^PliL+D@O2{%>V|>~{q#;wO#Ea;M(~ z7H`r0L~oqjh*<7NHV?|xg~-mepVM^VSS%HL5e8*elo zqC2AZ$8Eo&zhM>*75=dIt44m#HkOA0>>8n?t-1a$RgM~Gh}46bkdhIxDe~=s<1^mYC9G$m5%afN>o*=%FzoYpM+N`H; zl)v7v;}<)d-ju@K?=9$oM#@$GirynF1jh%j{^S{;qDXrgKsW|dV%hTV3+aD)g^mTqYfEi78YqDlZ7;o{C(( z_W?iXZd^7G@1YN?|K9)XFkWj`E3wlnTj3mb_21#!!yVGguct8pAr&}@HP_qwSn*;K z{jS(%*c_ZI`C31kzk;oC{tvbuWB=~%dFiFYU+pg6=Dl|02j8`S_whLz*#I}jS*w%>?gQHp}Ol%w1T^-~(N*^n#y!Y(; z0m)ki{Pe-UwvB&x=KSrx*Sx}Q$sGCA1IP4tcbGHx-Invt1pmZYXlRu-Z8nWB`lTN| zsUu2s^GC_}THD*FPCl_MIBM$T#|H28~7n9t(KOfm6{rq9b)i3o_zZ>SG`R(Ybj=m!r zThT)~qOle8&P`l=+w8}0-|<&(oH2CCDZ3wf;@>ZSqhX&nYimF0KXmZX6k8IfFTaO2 zqgOgV40^x{}Lo}3ZSh^4z?>3A}XXT;JgW9fLZoB53w zsg~xdr^gXio?h|E^;7Pt)&S+tykOnWzxCK#2Om52yjPBHytDrk*&ko|^SfsqdB*&8 zZ@uh$xBR+VUFqh_!H=w*KIO#4w;lUw<6(Qgx@5(Khxc5(*}O$>Bz}JKMbB?=$zhXj zKlQMW-05rIY+hMs+tK^pb8g|ZPoB*0IDPk{N1nFB`1jWO`M|pmSUTpGTmE)~`TK(R zFPnAMz8l@zFe-QO=5G#o^roM$_{rp+`~T{QfxRHGP^i{|7r|CO>b?E3bu6q9L z`*TwdICkKEv-W*>$v$fw@#NH-T23D^_v1aD?5Tw~{trKR((wo1*fQviKW(w=_;rKp zx~W?z&FjquD=n*k2c>!5d*>ZknvVay&+ey8iRbl_CixQm*136f|0vPTALZm$ky_D> z{OG2Y>RH+TOEx|A&=)T{bF)hh`RIoI`j48l)#P66j+p)j7w^04 zciVqs9+@(2&->5#$zE3-_2H<4I(~KetM}})=ub=ksXKtZ-i59N6 z$d&TNdK*Z+gw{)Fy%YLNy;M8b)Ag*mBWMyh+(6(+OS%)hMDUsP8Pc9xP(N>4;J9La zX)iNZ;DG;z$}dVwz0MX%n*_(hn$C7@FD2he?8Nt`*l(OpaC@CNpTOxviqm(d3LN)* z7uuVLc5k zb@!F^E|CoTEdr3z2>gvL4O(A20p8nQm#hntw!k0B>u_!A&YVgx%|EMeWIqo_*y9WCV8*a&*h~b9CYDvBoE{I_uBkq zj_aA5ZW6e&9Wsuy&XRPxAoG(`JlXbriFfSGDV}nQr%SJrddgqst{o+vTPW=$^D@r7 zJWcW#hI5hSL-?JDAB*#`hm0empe?E`XNiAgm_3dUM!*O4#o%`v` zbtydv-I0TT0rlUthCDM@bA979N5zVEWIj5x?UK%l+%``$%hRmlH7j^sl=--v4icA1KbaQkC!=x# z-(TMYqr5kvbeBzVd&b=CbRhpwyD!i>I{cWEZKtP3d@3 zI$qgFHj7)Nd}Qb0-U+I|1?c%nJUdlh8U4OsXG?+KoAeI^k1>0yGo$m7k@<5{Eg~Oc z^lwrMS4I8|nFpJnO3OT4ak-4&+JSMs+@l72MCZX%xwOkDX~)_*Hyj{o zmD>@W#^}+SRhp zv&hm_TH#L%{GG0j&-J*U&bqnMZ=K2!mHWoRg?)6E51zuM`$3()=Hr7J3@<(az)#ng zCocRz+JT>y?Nc*K?Qm-+#m7^{{&V~$#+#dwr(N$v_T?6RKzQk#yHMyp zca5acALBKn`U&3!=5&q7Z%5CYcjmNQPRg0A=%LE|yHqb~`K;>2r_1$R1$`#Y5_l@s zIibK~?dzH4_Mv&YccLL3!OD<^5xKFV*XJ__E$}49DxZJ86C= z?B!+xBVl-)cu)2LyaBRKb>#jGb1(Rv8 z(?ab*pSEayu|LQ@;rfWzp!!3!zDaFM<8`;R@qC8k9jSiEayBuB`NJf6KG435XM5?- z-2q*)9b@GCOMqMI`3FipPwt7f^-#W5;N59Dclk>8!J(yy!Q8%O4^Gm_71GTu8Y^5(R?DniQ#Z!`3I^zw)!Q`Prw(| zi}Q_>kT?oADbL7$Nah2^^9gE)*!9U3Yn1V`88SrDN{5bYO;TO{)$kMr{0B->x}zV4m?QOwRnxwD>#J|j6D zqtni0Amd4@UE&lZ?TFpi8H7B4_IsqAzLxjZ4)ezeo??8Wbno5@4umYQelPcSNSr3X z8UK;wE&YWgdhD-h+&)wPFuY^q#Vup*oiXE(!!Mt4%thm;JT+&h;QX~;I`v<>_VRwW zxE|p1$^iwa@fg^W0U7=B&5$h!=OePPJ?!(>;Oic~(}d|~x7R-O?D^ZCvS#CDlMk5l z;we)`J~`~R=YH|hR>4u)zQ<1&K0f*}O=KL73(WI5b>q&*`Si>A%?R)NOd=UPu_q5| z8G&c0=k+!Ih;%gT1C56=yFy*EesSb`s4KMHJB;u(^lc;FyN_ymhNhp@^d+L#+@+fS zy}tjAq+LD7ztpV9f=5(i_mGC_n?VXP&*>8-W z1mDYp^!^-QulAQWK<6pKyX*^je9_yRV>K7>D?S#&BFA>{3n#kkXRB7Td2Ag>Job&;hl3v&@6B@aTM*nMxPK@Sf`5C~0?rmVZps zw`lrpP2VbMtg2W)xG2YZ=uw@Q$0hB&rStM@NxPtR9_N#q?k8!QABJ<0wzE{*xl`Nu zo3?Y0zQ0o2xmVM>$J+UwC&7j(V`c<~_N7``s8 zx1P4MMB9B=(~tDA-8N|#tvWof1OV)CZ$E|Ss9xS5{U4&&8)aNTP!q446i)wGh11ee zhsX;bAs&pUrxXv6iHz^36;EGi`kB7qDSKt+T9FI(oGEUTU^>HBSf$;?nnw5BkKU&k z@%zx29Iqm;$>zKcbP&gI)##_ZKTr`04gx<`=!r5t0^w4n+~~4$ z5E4?(Btb8-zLjo_I`vv zymbWsi3XWxe6E4-{Yli?b1fJj@$qO_>BO&VgFl)9<+DnUztH*okT1f6j?}s=ost2Na(m(uEB;SoQTl!afB@Vwy$bCyg;XmT?W}CA9M|*0| zB>B7cpyPa5|Ksd?e8(7cg7@X{!TrthZl*1Vb4w@(`-blyOn^q05WY-ZhT;4Q-$}X< zNzcqBy-V+DJnoChJe@Oe-^V?GIEUxpJELm%w3FSF2y~ub#Qn=CXVcsg@EN6_Nr~RW z_mc=N_k+w_IWL!-;6V1^cQo5k&w_OsAGmLf%Spdz>0m8)5SLr3c7ehDh+GbE0zUUc zJfGn9+PK_vww&39(}TT$+V=+F{1(cak)r3l0W~dL|0B~yc=o3&ofHyX3{R-{GzNYs z@Fn(PycL8my#dL9#*cGw^nGr`8v1!E=bWZ|)R8e#FRSO_ZqLg1b}}#PJ!SkoFy~}D zFS38(oTv3oM$f@zWqoXF^&C*`?Q#yN_I5c3g!@A<&yLCJcb+l}G=G8KbMms{@lv*H zI{ZN30_SW)@C*NV18|P%ax%VPAPvI5BQrd1Z-CC5y{E!t#9wT1iWl=u?q_Zf?#2+~ z2j9&t!>409$i620GW~`6omsg*hUDx8_+kWpb%O{mfP>B#CdnRjE&+8~cuaa9;$P<$ z#SiXP^^Tx=S@rW@n-TnGKUe&oOYk{2X}=&dI$!JDuk}6556N{uGnd;*0u)MnS@9#8 zdcC)!{tm%o{T+S8_h*W4`F@8aQ~v;-yE#p|43x!N;#ydoWZ|@U$jed1HUZun}c?E9Qyu6?RT)Wo482q0esa? z$II}zfVbd)4-@#G=_C;TAvj3iBo?4Al25-qx(crPjJ1sT>{SN*8VKFA1dtz zqxyX-hv9T#Cv*RIY5&)2|98jwA8pv*O0uGv@v@5jm#uHv4*-8W#{XhmuPK~yyCfug zgQf-W4}{%uGDy81$t3cL&N0*eNcy*E$1t60J@+3nUaH?M{+?63z$Zd@sakHESh>}O zpJ$|9^via>`y$ruB;N^Mv7Z`QzaA#^fa%HYn6ErJF?n54|SS6k|$zslDud58E>oAj+J>a(Y-jiYGVBFqrZvh?+ewpq0)h_=0neW*5 z)Bo-HemgebUz^We>;ElZ&qecx`#WL(5}HZwd+_73JQ=!5^itgpxKilRSbbSdzmi;q zoU?kyT`u!n^wU(yQ`;`bqbC%cREhi3-C zpZ<*6Q3N3Z`66E`}#1i<}ppzNZPjN(Yvshx(F0j7U`;#EgS#G)WU3-600F7zfKdW5SeJb}aiWd-)tQS{pf5IyOjf9Ub zr-|vWfZonUxra#N?rW+)>0V^yV?`L^$b_p4V?|VP1Fv&ozfdf(J747~-t&6mX@w^Q9T1+a-S>&=UxZuf`ikwNigBtP zMCexgube)*B7E_^ATI4Y!oL;m8zOv$;P?82PMXnw664qycBqV_2oLOxR)P-zGCwPw z!8&C7hWs%z=zY2W2Yw$v;t>6=Dx1<5ytM_ zb6t_ksvlq%NjfGsU`EQh$j9tCOWxO@end{+XwONq9Rwz){_%1?>|WR)h!3&)rb))% ztGr?SxNoSu>22Jh>N%Cy?h5G_xBAHah2tEiH}`*{`5n%5G+Oij7|S0j`DT6OGkwVS z03!Rt;>*`^L9AQ_T{r=a+l{R^-Op^ln4f_LKVRY!Iue#YbpPLzc?n#bzXr({|2WmJ zBLxm`fanMJBc)r|BiE69h}*x-m?9ciHT$;!Xz6`u#4Z{Sxz2d-dA|d4HY9lpJ`PDg znbW7F=z8UBfg#C4@93JpYL(>3wbvLC_zaIY4a z2j4{cE8u=WBxn$Pg||)6UqAu|PH8-OoMA%jfWVXSuCVYLmZM%B;32#j>_=^be^(&x zo5op)PFqxOU^;6RppkrQK84+|ZwP&hlB2AM~oj^Xw8m!~iL zuRxpB7yOvO4Wzs|lIihfh8IX=d;odC4ddI9I9Z^G;P>hsi`Q?)e`5E83NF(dO$T<$ z=x6nw=F9g94f&Hv&kdIGTe)C-q*?#D%^ZK*#dJmOh+ge*Z98%gb>uIFyx)q7Ijkq~ z2j36sU2N$As3H8qUXu0?OyI-{m2c*GL0r4ybqL`#0Sas08KXdb2Tv;KZ-IX!dj#^F z;>AwD%-5IXTXng|GBZaC(qUQ z+J1$7)+aO#eX`sCAbM2p zb1U%^>;00@&$1sK7?IyaIp>8muY=3)mrU$o=e=+DOCnlc*G=Ffn*W2M`rpL;k^mSC zG8o7KdGI<)*F}kOwqHm#(4G4t@B+I(afZSdO1||&yYGOmo2fbFGwX*QfPB!CyG-d< zzo+fKnQY>9M5y+N@sA|FNKCf*eskVN_y^t!uR{35?`A$b( z?uD$Rv&f$1^KR1E3{1W0{%eR(4*)A{PUuY7-gI#Y&`vBJe`&Jb^uNT8J zF0vr};Cq|gUIO~NmHJ0~1FwHQpDY*St%G&e>GE{2eghZn;b0E%y$*hlW@b9q0{(kc ze(1ig(S2T8-ZMWs^?ILm{l_ls9QUvFA7dvO`qG~(@+{IHd-6Ia(EVvA+ad4G=RBX+ zE1uwXp?&|vUAo>?c+E=K9T#>3cR|}T_PZ=P4r8Q0N5+eBg&Y@C+{d2D@R&4zFR)^N ze>;p1^b5S${e89P@SPHj)0`gP-_J^U#8m`R??$!fxL#z>>Ha>x-Y0&as2{_24c7Ms z?ia`A7@vR3hK$buc!AyMeq#6e6~9rrjFi)TerkV)=#6go`4hj>{_Q^h3S9@p;PD33 z6`23u)A`Z)Cpd`j6Q3%7$La2E%a55l){9@0?r0fGde8O~(VgS3VE>pM_uoN(L~q7_ zne}VYUg??MKGyLdo`vX-*pu!vvi0)*QX6-(r`m%Yca!WN;e+q@ zPHt%H`x@7j(YU6p#@S@&iN7T~Py8?76XJa`R_~fhf2Qr4)O`FT0XLa zjQKXh;cK}dR&KuOl685FU6*%}+^kri{zJ+|>yXfAI8CVhO!f@-U%MCK+x--2fpc%c zLz?-cLjD|W>wPowCwVl$Ao&pWQ$_x)jLDzRs>q*}w%t_Z)|m9IO4S z{7FKlURMcE6lat?Md`rGAHYF)OrF!d{7Fu;^qZ+)CHZry${+0~l0T8&^HuMuoc=tL zLw^#w?wj6A&d_nG-b=#XXT0?;hqRwQ%Aw*sTfN8Mf32Xq@pVeCejV{47<#qpy(H`j zraM|$gSRX6-4`)>`)3t;`yxhfXtzgtTNI8}FUnN&4ooPQ2ojLx(GClXNTC3-t>?3`>!g}pl9gi{Rar*T>)0g(sNBV*u zWj=4Satm^OUCi%Mv2-Z$lqT;NR4xx?IheQQa=Wu$Drmf;$sD74LiE}v7T-0h*J}0L z{A2oE;o7}buU*6AE$VsJYbnRpw|Xt1dM%le^?Q7sVD%dODx%j8RK0ef>a}sI*Y*&3 z?rx&+>UXMeGMx3CcLw_tjM-7k$-bTE1#Cxqz}2zv&Oyhtk{tB0uW|&Q6adKe;V;;U z)4S^U_`Ev4lCJ9uyrcSqwZ5l*2H$Uycv($*_B#cLTW5d8Dvww89Sm+JVEDw(18E`i zitoti{E8nrf&2*szr%i|YJPv@v)$v)PpjT_bv@#&+I~R%{N9(Kv!2fH!M-55=Y7kO z{b&P!2hlP4i%5@$cu(`w%YHF5ec${J@%=*hL8+hK&KY`7irlF1TgSeqR1vpfMmg$t z+EVF={kExA%NMYNcs>*A|4R;)bo{=5H_-Xz^Bt)lSh?;FS9%coA1wPP-T>X-*ZBwj zl>OX{3&aK}YkTehW%3p>!zzDcgPpetP!ngE;WR z)bYQAsB`o%h%({#K)jOsqU?jFlNMik9!%qoOcHm*n?0=HG&+2RMxS@yOn0cwmPUe`wq= zMlAc6QCtA-BiMuB)^efxk7!?Wn{MzE+Wo&(4g>`l4~8eWC|MAju!!gWSb>n&>a}1>WFUZQfBq`0Z(#*Y4vS z(9Sc`4g*w#o9VGHaSsGXE8xIxEXNO|(eVtTkJTJMK_V*JzBlrzAO^1(Q)Dv4gn zk4N-s{iy2q^9INAt$(#q=sXP=9L9I7y>G|r+4d-2w6q`FUU45+_VrhH+^~*!72_~~ zZ2Um|k^KFU&PQDCfGjjVc^8UbhgU@9qPvUgC6QNVwa9IPt6RC9{-LE8m6t~4B8m%q zdQZ!LODH=TrCzo1IqBQt^rG~+O7=uaKNOHp zdfLjdct0+NUaH5HPf_EWiHEcLxl#4AL+g@Cy$~C{aL!)AeL|C9hC=N zrk7NapCk{PfrYp{9umv9?R~TI6P@Dcf=21zK}9*3!TPV2fg6PT ziRi`3H9W}<^~4U%tZn733BCzCG=jru|Gn9vL~l&r|7ANg+V}6?4lRzWkMzEZ_Cf~! z0q1|ey+G?PqI7zaT!!8kd7b$&)1hnSbY=#pi7(3K^lD!JWKOVr7NdJze`U^7y0>v9 z&)PAi>pR$>_=#Or)F1!Z^&Q{8Ua3cl@b{6g zHUTMhC-=V-%eQi$^b7Pz=F%#BwNb2|)k_=4@@;#hUwUe9!&p7r-mqA{ZI8;MJ^a22 zyPos~@hSF^pvSqNc>JRq%6_>UX#8U!@sFTWD(^tS2z(qLo}Fsfk2c=U8KQnF_Wx!c zw0vmSm*^eqz|0cKcRvFhJbw@!0|~ffvpiEa-cjQ2D&rl8ayxekA7>ts`HbQnku82( z=?~h?j+f&cZFyRja(^S;kjF>+Rp$~M{>2<$VO;r6g)zHOe_WpN0EYvf@NWgQ@0dLX z3`KIFLpwR_6S~lV4UN=6c3p{E1yL7KH)vqX;hHL;cc3h@6CSgc-lBUIcsVB z1>YAJ(Uozm-;?Pn3vy{?I3s#ud}ecsPa8*T=e6(j#PPG4e+fNhVW&ChryF|W{^Imx z%NOa%6M8bV4(p382n^mLTYKiP#sPjdceQ#huYF*ZUZ#$)oX;!)#3L&v6PFnSymHQfa4|#9n?<(rA z-&=k3yM>`2Kda&AKZch%F}hH^;88Zt?8UIV8VPEUIO&n79MZstO=mAhxgnJ5rQDTZ zlr4crJ%48L$t`vJ4}Bdi-OKkj_n0(c+Jx4L6HabDa$+04N~vF>3?>{OOt7yeOhU*= z-+wbvv|x*4Oz~fUjDpQPeZ$2gKbti8JI9RQV$JRM|Hnz^W*%#M<+6*{$}jz5``yB! zbgnJlM97%74fODw_Q~IO?cudqkhuDRp58z5;fKHI>HQ0d+m`n7{<)ss_x7z=^#4## z^_RU>yRfJC7$0o1>c7sx$J|l={eQRmTVpPJ>8j?Q-h)1N>*@VzOMltZYcq4FNErKQ#47UwGdmeYJO-A^TueIzj*YRe8nPqhBAI?dd(nw|7tPhwt=GkAh>{(eL!{ zss4bqYhUf@{rXFY32sT3XU-DnCV~JP&*W0(A z*UTJKM!ywbyF^Aetp~^}*!+`@oqhk>qknP5vzJ_U!CrH>A9nbWjc303gU9bkKXdHi zmmP4?mJqIS`Rd!c^o+kmR!I5wH)+ZToog(*|Ma~F=eMi>sG;GA>p$;wHte_O+E`d?d^N3-PGTuw1LxONQ-?hwWGAG{$1!MuiGp%y@EgT+|oAn zxCzsCfW|x#s&rg%{Dh+?wN5)>YFoF}#+Bxd88!NV<{oOE(0vVm!;T*o0Gg;^ z@sF9sKXFAfeM{WJ-!{E{!qm2L!ML{8aT6y@s?v2(TEE)gF|=1nBAU45SFNq8`Ea0B zd#BBwe$R)=MQAnAW!)d$k+3&M`f(1LB z^pE$a?J)4b_xF9IYv#tCjqCS2@2^|mIpBvg7WYzrw}%edef_g<*m(SVKRPk+yEfYB z-9h1rvWb22MuO?^x>FTjJoqSy4 zxsCU~{Ok|!AN*Fs8r!@*KR@;f_q$JrpAGr!q_xLhedt*if8O+BXD{{lJN(a2zcK8= zY{C=M?>mPo@nVbIM zmG8IyW{;NbhHtq;@cl{Aa-q9n*yiX-qb$%LJ^hrv->C1Y5Z?9nJ^3otu~d`a zzx%!F6LO9434X1gUt0RA!{#O4o_gGQ_aFL~(RGPmEpE7U)L+v(KK9nEAwN0s!2R0} z(D|J(>G%_lI%>k$32l?66{i>tm59$(`Ex}tm3nF4eD&I2_rHDJH-CNAghv*R|L1md zZn^9`^ZzpTvFp~}=m3$kfBnRx_iu1~F92x!t?lhoC!g3>*#$u8v4vRuiXjjV zdcvXBpEeb!I;w4I1rB@ZUiAMCK~z7EBf?=%1Bmw@AHd_k-PM`(>5!c+KJ6!WzWRjM z^^UpsfTzwFw&N+EzvLf&){X7w9slUBu3G%%&iift%wC@!`pii~u03U)tLi_0YQg-& z_y5j_uqyj2-O@f@kH2{zKm3z3$31hyv;XtQ_YZ#m=3O_LGV7)}zdzwmA5Fey!5$k- zKOBN!&G6bkP9Js3A@9ui+1ux~UAXY7Sr49a`)51dwe^yTzuGciR4~{MAwMn7=8C^R zUv{4e>|(FMYbRjj9vt|MH7#-KW>7-TrSvm=zYqB?n@P9SB$~`TJZmfR1J182=3mFU zNd1v^A9G#!5zj?^8;=rdd`e#SBi20xV$@HhR>zZiV+v~0c<^8^WHQw6?^yj1eCrXD#?|)pO-|^ee@clOi^T>Yi{WT3*;q(ETM*Ss3 zmqX+2fxZxPf*Q9Nd}&ASowMy!qsx|9 z{n2r_Y@DBw{TIe)3cZIV8rM);U-|#QB7PbP zKSMzMG(UtX+FwM!GbrD;`M%sIK<8w4bq-L+RfQq<^Up_0BD(KQFFf*N0dB^_*QRs%4hFTu^H2wPiSN#_cy=s)guXkf;0OB-O9&o| z7o11r^jP49#$)-%Q9R95exm(cymxlQ43hjE=i2fT2Ue|Iyxp0=6Y1M{d$vDgzS(&x znGd4NNIraD#uttMpxAgV9p9?+_qFNRvve%?TUmK_b2LwK|42CzBoYJj9Ojg@e1Z} zg`e&*CHP}}=wKVUmFV3?KP2al5I%P{Qn?c1y*PVSj<*0S`|>%A}A6J0sbK}%?y`^xuZ@E7Y(XMNj_ zvmfh);ysdj&M^_YM^fwpU`_9lbeiQ{r_(IwI-UKTYtcF^cgAC0zcU-Bx3HYvB0L;v z=~n5d8EMORB;ROF(2Vie`BS_(TVmcC@xGURT4y_y8`6hz=#%zytsE=q50$(7kx_|! z&`H@ZL>pxp)e`TleIjhZ>c5j7`;7Rk>Tj>HYkgepr8RgzS~~rSYGtbAlY}hTcirx2 z*q3I)#ILDX<7ZdtbRA|qb%TXMU*K2BN|g2xbkAgJh|KYU#&pT;rZ)d*s8Ox^^CK7^LLGU^QeXKHcQk0u0OG~E&V&dv!H+X`bdAi$*&ho ze}4EcH@$V%HJ`t+LGtETuifOF?|gdPdB=R(GHh4qB!9D4{&M<|y|2A-6;X38lknvZg5?T9=>HE~PX2q@7es=k!^lPg>Veew{wa5~VbxDL-Mk%lgiH0r7^{ zAXPxuB(FjvabkK8qd8pVudeIpJIT!?kBu+-e}&wK;Vnl^vbWpN0p5FWXd1eJ({D-I z0VJ*T{%uXaujzLrP3eDx0ik( z8MvJDt}UnXJHQA@Zc{zzEZ~XP6W$Bu>jCcrwKq6Evc9E0L_NZ`BYqv~pT>&}wU^{v znE!2EFQ+l}EFax1RsVDwS6$J+)yE(qg^%qb&{wy1kzb>5+Vk!(p0z`HT}$t0&^+1qzJ2cvmvv_i zuiw#L7twh|dqZoby@c1r>sI6aj@P+Zp_3gGl?PZvhyEa+?fXIs22tZ@0E!sUkwhes3+H)#6N99$OW;8HaIJYMxg zRd+n|Y2H5?{}0+}#*<1U<6e<#AN%%Y{(OHl@q4NNRIWbJIdN^mAL8QJ-_Llmxvmr0 zJ6l42B&bL0Nqlh#PX~SjnvQ?vh0g2D-$=f2Ke!LH+7+ME?;+I5&ciweK39hb0=?s& zPnB=u-J6Mfk|7gZwIcTn)l2gI%HPO((Ef3uw{v>r{*%7T^gO^JG+*}%qYF4%DE&99 zS2;=SfHFMjc<*rE@izQCPW1bp<3!)beY}8&cjY0u-=%V_ye}Us<;ovWK70(@0m{b{ z;M_35b*@ikzTl@ck2=0zfa{}%J_NV=QHC^F?dK2pdE&K?42J1-lCjC~huFO~5fVDfo>!oz3dIOy?AifB_ zf#+j-L!#eDaYA2MzX{S6)I_E$;3mu8HKZqhkm73{pe6cry-HV?>3x7b*DGBeMDz9g zXXy&JISanKDW)snP#Dfw9ZOrbbQ)V8tc~;zT*4F!oOKw5xz|l9+$+g;Yua-+w=Y7z~e<3xiZLjPxIw` zSH}J1g2x;W2z0Z==$iFV072p7A*Yot+v59`|4Z=w_6g!U^iHB<{8*0o`_>Y^@3)%x zj^$$cFLE9ip+Mx&I@@=y_+I#i{sYcPbP)I-#Hy6HbDX(){f`ShM0+Uy59aW{P@ZJ) ziuia}_E$VD;1%N^rI%wpl8rIW8d_04nrtHJAm_T3BU46~8Tmr;qmz6gx)gp;?oPf$ z{Q{R{HsBBak>tyn1eX_>e2E4pMsHCyOK-R}Z0*Pw;L|>EDdYE z$GW3EvkzC(&*WO*JWBc-`cGBwt7A;$KN`{JRKx#84fqqkzzoIT)B=C_?F9Z}heaa) z39eEvmU}9DqBL5HW>&#ZCGcl`s0sdJr=(^NVE@nd)GzFKvb$Q>i|+?0v==ta*$bwB zecHw)>loy~l-Um%y^G{$C%udKzvx|+uJkUF2LXKIt9t%T*s6?>kw~gDz|E>#83j{)MES>TmH=;PaON4!R#C{$=O#W;f@{ zHTao+pUwCz@j2#a@WZs_Q;FOs(Sv;i%+G}XW7SyiOpYv==K#h@{G9epfRDr9l9^|S z*SlzUzVC8bl5+X`N>$o789!Z?eMqE&L;gS?muIO*?;q~@fxPcIQ~X^$J?y7@y0^~o za(!>%`wXyykK`Ndcdj2ljqe52D2*LX{Jvk`_p{$Wei{-7>xc6FquehlqFnI*;5#4f z`bCA8(*1em>6j+VkLaab&R3T5yqtTG+KCrhKJ1YfQ9t4@AwM+aZ$$nie>uJ8@e+TW z=H&al)A0ni^cRSKF8U6)5B!RcrSi7Fi~XSJNcAQc&v&OG`#wAwt_k1M8$VqVymICJ z6;AsLJb=NNcl>l>aAH3x-st<-;4~y~O5#z06YncfIFTLG%j0#?chMfn0r8g(L6hAH zDd%-R8p~O9DtsXRt>6jLjr}(8hcO;4z!Swf#*>~AIaufYH%)$4^+RGcRBu-KP7M+s zlwm-zJ%|OQXAj^{&kCl3C(Ub^)-~;8e@8MSjB{h+KQ8ejJg-#s80AZ@u|A5Yx;_hI zDR2JDYE&1v#0nSj=ePJ5mn2q?zN_XM-xtf)LyqjD1mL(`pU^%@`Yp)d^Rj$*tjLv1Z5<8ofFH0)0En}gp67y|fp35u@6!UlDc-?KOX$Z#=X6co9`2ErVHXLjq$}L9Urel7~lQ6?jz_&)?+_j$0zi?;e(TX zr^!nIH^^RZ_~qioJicEW9+wlDCvIxzc^_96zklUy^-u0Va0DKSem}R&`{m=mTK!~y z@%xw5?>|NNoDV8}P~4R0O7Al^dL}q>TqY4aCD~NRH&^g1d5Oe(;oIePOt5dB@B#Oe zu)jS1KJ#tjGkwH&;_ss$%6D;JN^nVaKdzHFG0JE4g;ColC--&WtG%<_WvFNV9vUFv zNV1anKvGsd6TY{M&=K(|#UI)W>j}>s^q*O+ap7WqgLn|&Q@U09iGR-X!u{%sx2YRp zyU5#joZLgazDrcD9IE~5_ru~}xBq@v@(El2arcq&_rqw9>Pnh7>hZiLyZu*Bar>{! zwx7^_`-C5LwX0z-F4J_sfW%%<7{U`8%OM&Jun*Mg9wm%i6t2I$nei8IK$GuE|^RUnDOU zz6$!7=db2@>pjD<#2s_?ZvsBR^B}(l=xi1A8rE0y^;687=&rFJu%msz^eOvOn6HF5 z+-P>6V&VRe>5#94M+Z(3T>v&jclfcKOO!95w_)Ymi$otRPwIF3z#p^lNVD(=bRqcH z@CPaUlYZzy1Q*qVTKKKmXR+|n(B3F!Lvk@ydZf6X$P4*C!U*{CPGG7;2SP7C5&cWb zRXHC-q512;zs~w`PS4Ey`4Uu-e&im=DrgRN3pLYG-|Tk(QkVzy5%KTj{6SsPN06WG z{X|{U39TE=cSzwK^wgXl$?-mvnzjaPuq*1oi-w+5pvQsEd3^29 z;gEg7{0{OlC4DgHb%T}pj6Y#t7}`0F`jh=a82?h?hvhv~kJ}@!XQnl_S1O8MtUnQZ zX1(j_*i~ixLmliN;BUlsEbvG5d0Sr~6!ZwJ& z$nVSR317(DOJet+wFLCDfSMf#f7xE@-cF+N4HG{~#oyruy}-K^=#=?wf^A1i|Jid3 zt$rKwmjwAo{u`<1W6|48FM}S{1)q-pl(YYAKY*A0gyjQt{PbZX;6Lz3^gz?E^gXaD zb02IA{~DEX1wGLA1(Lpq^-PFvfG3XQig;psmEbd`3V&99pCIi=BR)^?^$qw2xFj1G zTy%eZqHu5USArw-UMshn@Y8)UE%x*3?x(2q%e*V4r8NK2S*CZ|KBeA%v0Kj7{Yt$z zsNHffF>0@0k5i# z<1w_}HG1!SzwEa%{(yBPzTiFIml~a-crnR(XbJt(xPo4vJBJegy8Stn(ig)zc(FgB zu+FA=w0wV<&!Lo5UyZ+?J%>_y*xH%eb12De3P0k9&3qJp)BKHZ=K1N`;-m5Jjc#-F zo#U7A{|VpvgXs0~ABEq!LG|m}y_??$eqBLA3;)e&;R_MrVYCsgS7-U)BD8-C>W{}) zVP6ds9>+SqJYUuK0sr&s5J~;`&oVC>Sh6AhW&b~oKT?0NjMuFbliw4axp~-^4}YCo z%U{F4 zHITy#O&&l_s$ZG)Vw2A~Jc@sZabh0$iF~H@fL#^r#biSG1im&wIsL5QZM_fhr2VjY zIepZ`@Tv4IrSB=?U*$!@Cy4KP!t<%>=kT@E&+)l_U`G=lhu@6+oInous`yEy|04QP z`CTHXl1T9;*fV;sF1?3*PxAI0{B7}LXifE{_^(3W@!xp;sh@S@&*NX*e-VSvgmbVI zNFhDx4R|#Z@ALYH%GJtAiYNBnyyw(Uuv=0kdXE2&@1T>vImP)5S~r*M0Xao_mBgK$ z)3^ux6)bN{e6P6K6~XW3-69X&qe+gZY#*9^*EJDf_I~DWN?{;ph=RJ8}1fov! zW&gL|THkN2=Rj-f&yoGjxp=3g{pb(Oti1QI_w~+ogKzLn68h*l{PX_uFe*@ZQe@-Y5I%Rt# zj;j2e8#BG2rS%9;2t5c-DGpe#(Gz$FI)eSI{>1WrYCrEM<$?NKKIZrh?0dD3V~f*d z;~;KcMBmlKxWsOOe$4YICG6jGyFp$P{jO5_?Liv&-a?nD6FTg{c#)5v#Kp?y??-$G zU73Qz!KPvNde@v)f8lZR(;Jb+IsZc&ex*5L8PtYru3w*-jN+~1;#_v)$Bz~k* zdA^`nrEi?0AU}|E@_hyHA(q!E)`RNV_&|#_tZ$2+L3$vSBYkB76&Jmz$WDz{GcHQ= zXa11p?ERGFHudYo-iMu%(I*P%EbOCoI3GdtUub#@w-fALwOp0;6IJugOABP<`EuO_7Hfx;Mewo5BYe}kmx&r zH`7({8)N+<=ASR}xN+>FkL?EWLi-y08hL7KG-0C(jBem$bIqzXL7Jn$2~Fd+P$zV~`2 z$*H>5Pa*%|UvJ@84LgGOLAKD%P@tR4cNJR7HRy}W1wUQEIpfL?1AfYRzee1Q;gsa< z#hT8W_%F>PzrQI9S1>L7BK(j3#dj0&BTQ2WqTWPB}gyn^q{L+^tQ>0N1&>fMV~?_R8W_ps{SV$Y|*Cx)-ao2riE zO+9DWIOMw}ZluA#lI;M`I;0oiPHaN;DY?&PNZIq+v z_C}DVesmv5vKjPO=v!f(N41?);`1%^od!N2`f!1K2E@9R!(H)-o)3g|n-o6L!dJTW zL(%>=z_X{088W?@eJJv}XQ_=-?ZR>7b5s%=!57%x2hj_k`sgaz}G3jgFo3ic8Lc#cfjx?fepIq1w{!SO)(xohI2m!c+j%msp<#S z#`@N1-E;A_e!QdkgnJ%rAC=)j8SoeXg3k z%K9#xqVaIj z5AXL`HnfxuKgw^6u{F6e35eL~dE0KuWl{d9T16AiREe%kxrGTzCKd-VtX z-R6sqNq!rAQuRj#{}Jqoc5$!Z$5Fn#dT}q3zpKwdX8KeaKj=PIjPad4diC6c3&fp9ck;eCPCw`X%90KVkc&qkaHU7VW|H=u%KL%>hI*qpvfZ}=Zm-HVO{3UAtC+sh2yzd_5@? zE%x=V1L`mEmlVY@ zz+;VHQhFuu6;hDmCcs|Eckne>eA}!4g!SCMopn4;&#{r@WdDi9D@#4nfBU#)AAEt_ zPw*pa+|ormQoTNt^9_Hpw%0yx*_U#A#2@%OyLhsl+PANb^M6h%+%l9ed5K$=qAK<4)>7P(`e%I3#Vx};i{h3p;q#(}c8PO4vVK_*uMG3ncwpb1 zfiEe&WaE{gdmyESY2lks7(W7Q1)p=( z@(}lnfl2&v_2QC!kC;63Cvki_?;jfq@yMm(cfdSrG|i}z(m2xKFYVSJ%H@r`!5j~YW@Ib4?YbrrbY+U%na>y8fTyV%q(t%Jq4 zTKui!#&IMb+EQ--?GoJ-#D9{PBOmkYN1VU|50jTk8a3E|2!73U7=OPc=NMDZ=XhH7 zPvzqeDeN4=YnL2F^XlKn@GZn4iuU31Ilg}IOyXlykH@tI@D$_3kM~XVA0_X{JMT8( zes|3EyJdjK`b2kthx$`wzX|-MX@ofXl*0oyCBXwfj?45PX7Fr@%S`I|s{S7-{Ar($ zz@Om|{EmhGrYwB&_gJ>z={WI(u%FMl7wI~%9fW<_hL=O_*C88k=zp)lt-E+b|GNU* zS&!>K&+4z8ctf}QDf+)*;~JN~c%OU!MT+OBNAxvi{amw_`nhE-_4CEG)XziGPyQT2 z|D(3fQ+o~pe6DKpndM(YE_3`*-`-U*HIlcG2;Bc%+{ZxipQ_%2tnsXAGi@Ea^K(x_w=0GJv!8n!r9**0;v^nms=@<6@al zVLU5m-%R@YN<3@o))%&|&}oYC1r;$1mD3w?Dy>}a!p*IZ6z?9*01_l%z_+!6Lv{#hqG9(FUQ1Ky?W<6R`j z2M7-}-WBxr;OEQ6yFz)~GspT;IjZt=EycU2f2NyUyerJJ%Xn8PFZ2fhbdGn0d276D z#)g3}wT*X$@*3}=bdBj<&ijJk+0P!vQB71oo}BY}GtdF`H`Vd3BKom=WoX{RJnz+v zcjf2{ZlJE>T{Ct!egwT9@rcQacg;Y&fY;IeQ{r93ciYChX8c~~JC43e22Q7Y!@_x_ zqI-kD+Bn`-!BEIv>S-VE3hRyzUkLH88DACs(BfT|9xt9W<9frRasBKZ$C~jz;ZIc0 z1Mibu9Bani@_y3${Yb9gDT-rF7#=H(UjuQ-Km2)v=hVirX2Kq3_!q^o3jItk%sAEz z-Oma)y54I9`XjzpaL#oG&NZ$izDs^gg8LHz?k}iwt}{2cag9q~9LJjRw9Jd@(fW29 z$C_|I&#j$)X7;S5el}Q3{cI}z3cO$K(~# zOka|oxd7$nby4nh-Ic@6kLlF@%)L>b{f^`GkGKJS|9z%@chUBZ&_p`_*7Q5@SG;Mz zLpt^F>-w<|jFS%=zWDd+zaENML-FtTJ05v^ajtV1-=UrI@1FTzvpVJf;&*~;@|W=7vET%+lsvTvRn53 z2h(&F5|Wr|mDjb@_i^KJe)cAB%T??9P3kKjkk^zUGMM|C+DUoSuL5MIB{8 zb6Nen+Wc4@T z-r}#l@>}md>!QoXlOfG*<^Nu_>iOqdapU<6z3=_H(-9Xe{vCDvPv#frw#^~>thdW) z9gA~s`sowLwJST`J8f&UO}VuB-EO!4;dh7s_ziDA==>YDJ?9PI{l>$eIb_kgKV5vp zhG#9<)xUHYF@z33aEO!cdnE9Q4keqj2>{0b>_jR9J>$cg>8S@N*$RU=l_RmPJ&%r z{HsIq!@ISH`2$R=~&K{-^!#Hn!X29qH?; zH@v!|4tB^5w|5-hseK!C#J@$4op4|K5z5;aZ+22g9qvDWwcY(cxOnANm;7?}>;B`M zPrrPhMGs$i&?jDW(Oa*3=?+(X`Iy}xUjB2RtzYr@J-7aJrycLu=fDTAIs6M(o_F?k z@A~>q{^IAK{mIbI+PiS!$YFOCe#BOWl3CGGEYC&Ze#xzIzYXQk6V5|^5;jtr{QC4P z>l4l|PQ!}hXRki{^hy0t`Dyw@w3E{_^hsf6$|nt#p6lT0@x*wr)A&lH>gaMOJmE*? z^D4SO!R;V+7OZ=|A0Ya(`6c!H?qk?jPxHD2oRZ=JIO{S1`^#UZ=}(D0hHrXtKGkP` z!IGBt?EEU9BM9XZ`#!LJPaZI183jbA@p^Cf$FEE8Lp=}z`49aAa*u`w1Zp&{ucBU! z<|}b^f3>9H4fnY5i5j$V@Oj(NURCLvERB>ov( zFwFPrTczY?C&{!04sccQ{L@S5OU$8{5YKj4u8UzT#(@TD46 zb^oIF%ilY9enjp!^viYpr)&K|j0gK*s2^H4j5}Aql+d_oShOz&zdd;UFX8$`M{>XE z!dyG;#}T8v%x_7iJkcM02O+{!;->BEW1vbu^u2W%58taAgHl)0`1$+2crH^o*}RI{ zqxd)BcfLKqI@8{YF54TCa`6|fKH4XL!2O61HN&}@D4e4j->2#jmm@fTwWVCD>#5~< zJxXrOEsEX=B2xJK6%v1n?^XJ{M(D-p5AblrZ&Ip4^If6)t!#hR{Ys~wka~em2fL*s z;~&^R#rPt8p^kGaG;a^gW_Z%MJ%+cJ??H~}I7apRNU7#4-*97^PPH7yPw@`$(^*7E z{vt2u2S#O|u>Tt0E9T@LF_sIln^WGqjJflDV#;p`--iibz}R@aa_krOM@SzE|Q` zwy#;f1Ln5!vji^DEMNu}3iIUgfhLIG0t^H{|CLDiD%ex9T^l&03Wv`K945Yw#_t3l zA+v%!8KCjZD=XeQ{w`gE-`V@}u;6pKOmId096zmS{peKn$MJU`jn6@D>vvOh-+{vy z%wJFQ)N{(jhp_%I6zK`5xBVX`@P*^E{-W$TD4&Cq ze@Nk5cU6|p%akr2mi82$qz6Q94*I!M`!ReS4?nL~{G{&|?bL*qdcP9p&-dTDs?Z%q zq4ZL)AH9z6%m5GlOBJ8%P+ge5dcCZ}WxaCWbIIW4+iQfXhJaB24*4It# zK2`rS?I-LeyZp2syaW6hu;qv9k~NJjeB>Q zFKt>9I4Isb++xV@2XpTa@>Xt`>+cxSHBKJ{ykh$ycRXr=?@TaE9*3SRXBuSg%#c2|XVA@u~_az2s1Z_!lFUxEIibHcvM zmZNxZQ#o*tY<_<(e<4cN#T|`i6fH z-!F@ON_?E`9Fi-<&q@B2@x;#$#E;=I8Zm$L*Na}`uf_90Y6SYs z=e67|PW4#e)9V$Vz;`0ZouDTezdPFGYBZnd$lsW~57*x`vHNhLx1||=?@5vf|6d9G zLvG6YQSLt62s+;|<*(*_xX})ZXL-;SlnSn-qZ!R5|V`#U7x@ZDdf zU--AmTU}q!lgL5Smr$=8xvX|weX{JV3Q&af4E?@f^j}5~-JJ`Jz8mO??XBo% zR)1>kt>~}XPq4RCk8_dTXUqOvA`@OG&-dAqUM=>m+5e{Z>p5W3gPA{=o|r1$R$&J; z`?>V~Q?u9N4fLEa^*4L!^c7GmkB{^mPx7dQH>@Wr{oe0IUowAko*wCZRhAybU*De3 zIG?XidMWD1)3krxfA8;dSv#t%4gQ+7+}BD$`4p3L>@nh!Gy{$(gRvhH}kkMqsmOt~C73Hj@C zzR)qo5m$NqR(_zwX@56RsfJq49mC~|?Ns8#WLH}|Vh1$r)RVY=&MtKgyY)=Ue_`6G zo$R##vrol7ReB@4ko>55yENFZ6SPlbpfuCH>0iKc=z&CEjIVk7G{QKzyx6^k_Nn>J z)s8K+PxagmO^EfvdJezCeZQJNlFj#dze(z+)IPQE9rsXZ*41AN`2;^!jQ!MS(m3E&Wxu1w z@8X}RKAz|2H2>y)SE!%ye&N@7{S5Y*@aoc*U(-n{ia~YJg&ORjS)?|^}P6fBmQ2A|DTh|w_*K9=f^&gg71n2ASP#)-1~r{D=EM5JHJ}&5V+@1! zi$u#cgDQq^TQI*x zJd@$r545b&a>cz8+Cw_9y zTlpLYE8lSiKW8|Vex&>?cb~Dp9`J%6*I&zUqk5U|vP#@`e1BKznSmx2>vdk=LY{^y&&)pr{9GtW%w<%FLanHKS-5N zIwx_O6!gk;5e;ZOb6=)o^T)iv`KvSfHT|R)^iBL#<0uj6Sk|pa_^B@ssa)ZBJJyr^ zoyn2HPvfWXhSpi(<-ef(>@j^`RX#N;bceSVZ_C6V(1Psug8sw(S95Tt3+QQ_pmT=z zw@dPU;JJt|a9qgNDcO(tRWI9{`0j9JX?!g3@$X??6^Tm&F_^EN+e0{kxaF1n=?uRvZWP_d+kZ z4C2tv>&AdP^?x76SL1m|{JNj;rZ~=I_~6L@CF{m?RWwe2zsfQ7huC*H&3|0lHo#xw zMd+W`ul+shrTJm~*xo8(?6Q7q$A9cbn13|%H}DPk8-Yc@bI31$n>V@7`U>D8>w1yB z_fbmoQ#nu7CgOMWpCvkMviqU$MN84|ME7^}_RIS9mS}$LSA|XR?o=r74}qz~d8uC->>PLo8F8iV@sP-_rNJ zfiF}Os!wtW@Ne>yzKY+|J^A#W^m;0XU)Em(fwT!;MrdEkNjlD@wSSF|LQjGGfZUj@ z_;l&ZGOyt8O;vv|JAnA8@IjVew6B-Po5KrVWDk&^7V}H?!k7u)bYB7VY}uE9`L)xt zT^|;J`%M z?d189(ph;^quYP3UQOG0hiU$+8Apb668}>k&s@y=t za9--0eqADu7=GnRjU)TdDL(|C8z6XST)Dyb@EtMuB}&&RUgqMy<(JzyyNmmlw@|nN zO?aL=TK%;nx68Xi{~y3SC7wY5)%!+(zq2VR>GJL#C~*^2Q@I<%rC{1GB1h}NB!lEH2=aM@&^kuk@0~${_^Hd`8$1f zuM<1vzqxYRSDW+oY*OaYoZtMn75eQr-}zwk`!4%T}@4GsuTU4LMgx}S9&F;aS#3Bs>hyu=&>!{vtZ}S$Vu=0$t6dBsJSft zodfqX`Fu-LIs3cli6Zy>BiA63qs`@a-u{V`Kk|p_+*{T=94-}K56r#o{r`DJ&(A;eqa!ZA=hA~W+4x;Q{^Pq_!Jm0^ zWG2;`xUTh8=FM31Sla#lAHV*~)3^WYN0z?qz`?&h^z=>tc7JsJO`E;vPe0o3kip;e z?(E-$C75~g{fCZRdgRZ}`q(ue+v987ynEeuJvaT@U+lil_ct2pef?NtjZ}Yj8wC4G z?1DbWeg(Il=~aLk>oM8*9AL`#QN!ECdh08xY0_)Re^aBeW50Eko*~}0u7^K?+GRbo zs`d`457193*1D4QXS758&4J$ZN<2}H+Qq+_^OcVFtJIpxP0OXhM@jDq?Yso-%}3gP zc~nTb56jIxX_a!X^2w3>i(K0JE9Kr>vWh?Gzpj3lUODZ5F5mwA3m4Qj`s!x8#dB`j z{KCh-|F2sf`i6O{4m8{J(Io9aK@Bnfo1OIgt{&loP>uUR@uoBJRxpm;zT(2tS@NagVXx~8%^{qYB=k;Az z*8?^Puao85dmEv_&C+RRoql!fFQ47WzwD>metECkkGSL9Xa4r+;?Ipp@7u@*3O*i>l-uvDgKQ?{tOCS8~?sx3_+|X9qECz*N1Y1d- z^e>--c6G6%G5`6z&&lAmsm>c-UG|gB*8R!}O2W?*Ux8^eKI?v&G3)~e{`;3Wp)c(H zS&n=^SJTIcz2sl6Y1AK}{H-mhv)q&VV_7{ z_QCl#BEN>U7}j+!YG-39=kn(dY2OIpiNkzB%YGBtCtmk>-b3$UufPWNFV_0aej2Ucbeni%{e-jC$sdE&2(T{;`>(n-S`=!ppvzdb70$?Liw^85AZeFVQb z;gJX22;cpI=(*Ow3y=P2KML(pVf@@&(jGSpaC%EkpQP}9nWPP_Zm@+u4E`L?3FTbG zaf3SOjs2$ZV=;c?9BWK+!~u_Y!GrunjCXnt7SkjC67Xer>EHcW@nwOe{fl(Gdun=_ zj`y{a4&zv|hT|R8@hLnT=kxXabY0JfB(cs5-zCSq{N9L*UPAMX=5gGoqI}39u1NH< zFb4%6zp^f@~3`MG(celwpJcqBvQyCHv^-&^P#`ygb0it`)~ ziZ(?a;m?lRKFTv*?4s!lw7*w((;v+r<5&kKEcVl6@Sx%i--X}o?Trq_-%fBPTY5zC z82FT-U-Bn2-o#8Vn2*4FRLXMCm8S#k` z#aI148NFK$dIR;X&-Vd%U@-JTq#2IUX|~_UEoQk3UUepyS3D>ChVb6#8t~_M?ria| z``KR3C6DTUiTx!WF8h&yFNX@Xz@ez2*+7xp>j`*}I|Qorr! zBa-nMUZZB)a-Qrmng{hu^iJ}E;8n2RUF!!qe&`nqTxgyHJkJ9CA^6{QJvT8q>(|r$ z6>^^j=F^PtMe}oH-(Y^@!GY`Z{M+MsQTuuL`Mo>S3-%f3?GIa*tbT=kgQ7>V9s;BU z1D}I`5oc_AU$XapZx`$ghHqB?0Pz4|sE=~IpRTbEz>9YBdsOkl_G`l6+%ddXzL^Zse)Np~oa*<>s_;(9ym^0ipmTW4Xup@A z?f4u3>`meOsq91a`v~7p#lui$(10W>Rk$osD> zjpGut`1}r?OJ}$zqF1q3)bs2z=q&+vcTK=#eHpTr@AL}J&u)pSr71?pDEw@ zcjafscgUYW{KfQ&ME22O2E5PE4SL~AK7PO{2)6V^T)r!H>--@ zsjlCs;JqJ0iRSf3jgEA8WS4Z8nj8e&K!48Qc7x3qazWN-KEv}q)zgl4A}_x$>)}!_ z^GkJJRZZ(TH3v3FbcpkP(w^>H2OmvkpO)GuL2p%mhxaMgACt#_!0S97#K3J&_^<>; zYA98VuZ-{U>t$a4ZJo>e!?XF$@KNd7A0hdH9%vS_KBCW)_CV?UK1YfR5ImL7VE*%U z{_J1Gy@ZNKI)9oM;}s?_f$1D&cKuKdMMGrm<;zLgA#Up`sD z=RfMDp@{q$&dIq9Y_(%K@im2S1*R0^r{4cs$G*#^^8T&nXNxvd|AW|n#*e?L?WEvm zM92Szj2ic6a=5*Ib7EEw6B@D}XZjiADfEJ;!E)H2kMi(q70L_agM)EZ=A(AWacCFg zi9dz97@y1M4@o|9M4w2!)NjTs@VyvvK2txOgLey9KRE$>5&hsN`=OKEIEw6(;T#WRXLc7UPJ01#g*MNv5aFC{F5I>dIaZW1>W28x<8f6ujlf|a`{8K{2|GQKSBK2 zX)Zs_#4|n%Lo+??^XCu`JUD15*`2PxHk&IlYY&++~iG{|Mooo#mk>bsW9=;=3k0| zH>V5#e*a^O|6Z7hjO!QQ{Eklf-&^tUv`+cIov{^oR`Cz{e<;p%?)eRO;W&R$8T606 z6~(_F|ML2u7w1CZ|Nfn?8z=wuo$|l^>;p%4$_M=3*(v{?xldF(hX*lYe%n{0q-J5_DJm1OEP_ zIM=!H_Dt{owc8HuRR6RS&VlY#{CoGE+r6ha*E!_Bw^M%obL;NeDIfjc*(v|smw%@t zeVzF1VZzr68bSWf1zG(1o$t7=BmDt>hjps|#$Q}Dr&Ipyho2bklt2H;uYYlz{4aIN z-+sH_ccg#xe^jUXTmJ6kPj||X{&wJLo$||H{`U4A^Z#e@>230>cvrXgYr5~-+OcP| zyeW`Rsg*5wRtw%|WSRAoE88NN^1=({sr@nh$G`;r4fz&q0(MwX$vC zTSwlQ8GO+4{x-{DfzWpWsF4 zg7l13;||{OzAB`3Uo!j+=Oh0JxT$u<6D}RL%f?{Wk-u*{$NonLU7YdY^m`vWV5C~Yx-#`pWXAsxT&t8^dN@r|lIyQBIEcEa;PYA1JJy!q)* zKo1Nz_&J;Qo6&d}K2@Lq;E#W^=S#N5n;Keu2zD&d(6Sd89P|5KfH#aMACx!|&uBKr z{V>KqA5YyUO>pG(>=uq17pl`m1b~zCAO(6lEZ%HwP&O9U7NAt}OQ*RnFG?j&@+b&dIj!rA6WoheRTO&oXOA@3jo~u7`UD z_SAd4_RQYnm85d-k+13dnw0Z)j~DNQftOVLmqvg0YJax=U=8G7!LKvkLg#0^T2s9J zq~fi$bEU59s}(Qn>Tldt)3(lQYh4lN8D_fba$R8y2wZaO3O};6vzFHNY#oQe>jC(& z2C%B*__2WLa-Pia!Rj+gboz&6A@ZYKnUpCs_p)7byzj7g`cAs(Kh&;{qg@xo{qXs@sZTB%k70xf(Ev*H~ zYgrTcuUU>>8K>YkCiiKzoAF6nIUeP23#*SLF2_1@ii8C-Qo^;{AxG zQJ(3;TA#K)KqTfc)gE^e3LChacIw2SjZnR3o$`)$=X zWc(1;Xou#3cu+iqC;q_rd0iaA`5K9L1UN*#%HtgL7SG^&7DGM|ocH5ZP>=MfA2Pg2 z11Y0D((g*#?zLXzzUl*Y-QS4uuzo;cnEx=%JJtQ0H-zzsoJzdJld1p(%VF>@#7V+_DqBCG2i`;20;dUH z3$>o>uc?2Y_XBz8zALFe+W$cPJDe1KJs$b_4e36ED|LOZwEdbPz99Q3utW+U!E-BT za>;ovS7-eU@HtB1BYL?3KxpU&^Yy;OTUHUyS-0UF6P$J#aeH& z*D=L>34Vf~{0FV4|D(e7y0Cr{r?zn{2cBGl;<V*#JzSsvrWdE-E9N}TJ zJ`23zb*!R4;06B8>a+Tt9PCLGeRiShv#3|2@#p!>@*JPZ(tEdbjCRXa!Bf2-%E^91 z(687H=zl=qr*=b*e(%icvzhs?<6J#U&xG$p&t#VgKhEMoFWw8CCpc%b04aha>2-O0 zA60zsmY!35Pq7Ui&^=|mE)6`G&Gef13m7h0dZlq(!1mOYy|y3w0_MZ{_5TFuwT5xm zo$PyBrg*;sE06aLIAOiFLb)nTQ^dnv4@^_0^Y|uD_T${-R@T>@---Ex@qubd)(!gx z;$u+``#FYre&v$bf0f<>J$^&xXTQ{W(>jp;{+!j1x8wRje=o^>;;yuZ-bcEJ=;?Z1 z)tUYtFVOc3q+Gn8?la(h6IjPPm9Ke*udS=Wm*fffK8^v%dYHaPFSf>e(2}S5UFmKW z`1cF^hvHiq{w?d0$hx?ATOLOd{@|p&zZ!h*tpEhc0b2(;zy9GSK1cj@DF%&pSl_sk z_m!f8z|U`{;~v8L65p?bMGF6CJ|1na`QV>2e%sHFb7UmPi9cUg0m^pXO>h4&tY~;qCR3AL`qEm(DNuZ$tiWM%S^VQ_vmbQ@pjV8=n&= z^5l8J&P-{3*T{T`&ks;}?-qe_9Hm!MI@SHjF0P3kSy~}@U6yn2PVeE3D~|Bkzt-@u zr>pySJ$MB7qx-LDPw6m^dus0ArS?6w5B-7Z4$DvYiTfy1cRT?HFXn*9&Be?qzIb0e zpXr(|telPXjkt%HJl!!a-@~2N{ z|Jic)ZEiQnANSup|LSznuU!1yY^#xGXU3COZe_SRw}Z47e_PVA_OIg` z%9Sf)oLD25Rm`;`yO?H^_SGi3FXKl3`p6_FFJe>(G7*MGXmss7VNZi&Ct zyUJ4WM>Y zts?X3xm4#P-!X+9vXc22cnZz$w^qO99#6ejy!>VvXZg)CZpf2eP|sms;W-$O^NaQU zVksB=YrNkL{xy+9E&6S+A9x>AkROp3J>}+j7SDGsA?h^a|8q`G7RdKQHfx^!o;daMt~qc|BlM^<5`=0N=y2Ws}}AuD*hGV!0!H zGTKqcKZJD@I?d`SNo3>C**k&{xW7o|3HT5m2fJmi+OapQ{bYK@&8jb@rdK3p=dLe& z2KYkbXy_Gee~=zAK=5qUE94xHD~nvaInjImOSEqi%!lm^JmNi>&f+r5>u5j2pGuke zLIP8S)~WS-!hD}@v`@f4`7Y~LRzKNO_{wc0y_2BGV1JUWYoL4Ty6sQ>ml#&& zmtugK7e$GO&fbRfm%`ucrmyq<=1lXQHb0y5oBy^lYr5ZLqwwu zV{I&2)O*y!cOAb$wDX59cxuskXJPXB`42Xa;{$gu{z7xQi@!7VfAPEXH|$iW-YIjO zJiutIph#H2@#4`2=YnTZ`=Q%2qho$??;jui)jFkzFTUjsr=D`%?xR2d(oU7%-1h#v zpL^e7hi-_0Y2%d z`m@%@n;P=BK{<-gf|u}pf0nlT$eQpEug03x`pQ?X{_PY0S%sC#KazvWlk{HoI_eT0rs@|k zI@wg|gyO7Ak$;`}KgAzUa;e7pXw2WMabtQ<)(yaa+F$T8mD9IsdZGFyZqu~+r#>TT zI#x4G?R{3#)c#6J59a%WzR=`H4doKQD5h~b(R1DHsE2V-{s50t=T`;Z!LIdKbI$GNUcSSyiy^FyOJ+co+fJkWbKPIkYyLxFU-wXP2T^C$r z$9l9oN58MtcbVG3hp=66QNR;EU+J*#g6Yal?RSC5AIJTepL7iH_%MwhS^>>3b>i38 zc?bSDs^1sGd&cX!#tFT^r-?twdBzd-6Mzr1U1|Jl#7jS5XKaVkN}qs5en8=oV*QAp zeJc_@fS{+! zJ?dBRN#zL16XKh9qFu39F%yOFdA>f75O{RWRJ%fJ{Utc&;ye-fV1|!uUXk6%j0fsu0hKC8Y z;w$Lg{CRqw`8p?lk?SnZ?!{jCqSDz!@d0Sda1QjQ<5s-zso;g(k3{L^gbxqOx;lf` zS0%0MOzR?i->nH5(3$ z^@_0mLN_gX`)a{4zfLjcu_a)I_Z&YZdBA=YJe%b*@r$=&I5U(FqC$obOcp-n$7kb_ zaRMMp7ftePQs7|yQ8pxEyX!Vzx^igyGGM1 z30&bncUywa{zK)Y!iDgc_!#L?OwW1znCf*PzqsI|$&)ps>!S6h^&tFHIwpFcc?bUY zHKo5VDI9>0j6@FjkOzuLyN z5KHGclTbPNAhwpd_vb^|*{9hI4I`{az zD}K-^|GAAXJFrte##il>f6iNaf7B^|$Ls1J87H6iFBSd{_|L;PD$GR2AvfIq`A+#8 z9QoaMb;_TA^hNM>G=A-3N{!s`R~3KNEtOwcc+!23 z6pgc}i3AyS%RGJY%%|+{i9Rv*vGoq#|Lqg3NBxIi8~m(>O#Jc4%TGG-ki(B0diiAs z?f%6dTrqIyy89mV*e|!(IDT^MDgVQr&p&RX-~Y9_eE#o8qw|s*-njgeJA7#Ovo?Rn z8E=^P@GgJf^CNHj>;v(OHrjF1-|PTg@>@Gy_{~Khd-r*zH{G}RweS1hzr6V$&n1t{ zopbpPuloAlPmXfj>^REQ`MU_ ze%kvj^=HArV10iKdMa$>rsyVU&*^Q{SHlkK-1uL%h7&vwxs>Jnt!gOlhZ_g zCFe<+`s48o!k-4&*3`eno7fHt<&w-jfC9_2swhn(z>{VR}p$d`Rn*f%_kc6|!{o8acmzwl1oH&*_D`pY+G`x0?ho)JkxL*hU8X~OmWXj%LXBQ9nCF#4xw@@CjA zHMCOK^nsd29D?E+XyccK4!$;*5e zVxBxNz-I%@e>&C;xSx#aINu}v_#qwFdnFxzSI2dxq>~jouJ=hg^uH{-zJ>S=|4s~g zMSkWSTYkNB-u-wE`@bdnsP{!Z#w{Nw|KKa~vy zf06@jd|CUk{2OgYzi(ARKP$1WV1GQ`u==z@^>G%m<{}Uyhz`rz1c>Oj>hxMN@e=I9p+)y+9yKTJUslXvQ zprrAY0Jja8?tsn=hhwa~_-m6xbzhE)j#B;;{J~`R6t3stx}G`zMshLWR0Ew8ucdjP zFY`_wQaF4_)BmOE|B!Tm3$1t4zJ&0;WuEB&608f!g(mzc+dMBJJfnR1E{$8|<`bTk zKcx6G>;=E;DsQStQ~70@rhPV)ULomZrIx>6)1Q*`I(x#tnvZ#&<`^H6pX)gE+^#06Uu~|Psf0SRzoFgT^AoPB9R*ryw zR54!&L*W|;A6L~r=kui2-?)6i;5apW0m4SdKOTP?@v?rCJ`TtO;)7r)>OW=tOD=<- z{xW_)kM{W!-xYmB)}Qfo7UjDs;bSf9l0c3xKyUS_f`jNYIBvuD-dBF7{3Qkb65Zbz z_^ig+)&JfL0tFu}|55qFSwa^H{GCiEh=&k=Oa83s_e(m^4VJ$%e^9=5KuO|GP=Ay^ zY?tMO7zgtQ(PNTB?OYJfRcwO(17CD&#t)AXeux>f-!kyUr82J)D3a%WlBNeVeR7xc zR(_tqXW|L&AJldZ({|3uwX-6}cZh!qzs>5^ldX3w%T9$e5y5xf( zR?MIHV3Og34L*|RgC$RVFDAW)>&1ZA2@QR_h0X{cUr2hVTNbb6 z9M)W4rE#vY(*`q7V2K!Cn#i0R1yv zA-coQ($V@X>ALSP>mJK^@tr$f_Yc^*M*~vcMO@AazoYJP6FtG3{dIlIwZFNx-ijB? zG@ZBahm?=1UI#wQekS-Y^&SU3m&f-3pqo6O9?kMsQVWt<;Hx{M>`aVH_QQH=*SEPJ zg~q!Qc#Zz%vpuN$iIV->?Z}2pK9}IkkXA8gL^PQ(3td_?3F>to6P=~c46FEG6-ena3>a-Mb^y$YvsGkR5gpw<6By-MY5 zTfHhiEx>8D^eXxuT#e|mp|Ag+UPbUoU)XvTeIIL>*KPImD*E1=tosD@DhIfbzh%Pu zmFVNpgHra7?VG56WpPJeBme#Q`V|HM{kIRkbCxHpZy|lT>NjVIK2uWtIH~9Kn?cbN zN#D`=7x<4daVAVy?a_x-o>n!Tn!b(qv!VCFGemk{@MpbM$MXwWujJn*Wjv&Jiyu4} zPfb3Ianz7o*YTPDzD(1o*3llhK4AFeQjoU0vw(2EW{<#U_bGG!{lQH_^0OzCyaMJAQjdZOQ(^Y zl&D`Bfl)nQc&?r=?7czvOB~Gjc2Exg-ty1Ezl!nsEdEhkSNuXLD3A2*Jift=CG!@$ z-SDomK;pfGmwwlE7WS*vA5U3O~MYtqdRMO0?4w&k6Bli-TMJ_-KuT%Xw((pZrf6 zPl9}HzrVr!!ufc(#(ymCQ#c=iQ$J65j_bqfcZO5Hz$qD1ICU$RN#2vad6;ATD1T7t zG-;OC>nxl`WoXqfE~5K&_CTEbm6GnT&g*f7IXHV-#b2G)c|94wD~bM+d{pJ-i?v^s zzsV)&zt%B zv(_07>vV=gasc=_?Q}?d*CmD|0w^+?I(kORQDmt{+P!88=S8v z_<3OL`H;iFnPH||a0=2puJmpQ>zCjj-cdZmJgxxzkq-Q$+j)d>u>XYkC+iKypKU(> zba_wbiwKV*oYOrPPvy(_gU@A`pHaRvUc7DuaH4i5T`%cw|AOte!adtMAK(Gchv5$d z!aTPGzu2~bUo4y`elq?w$wk9E?l0(xE$jUm!6!hfySTw;MNg{t(>gWj>8iIj_M-{E zB!&!o*$HEs9#VMKB~1tKcpX#CN4k@Khg%UPt}$Mmk>nrbJ&}R( zb&^jNykNbeG7$7I(Q_Wsaa6k;$06O0L*Zlc*!E9o90S9doc(1-fU(ywJNgbd@Gis& zY~7$QG5@Jxo?8-Jbe?&;b3FL3>AYbIi5`;sE_8}@68^~dH@D~$sl}y%ciqIJkJk0c z>m9c@!wFF6Ei*jE)kAV}G{_&-8_*K(UmACx-QCLHa8EDGFOTOvv>ez6unGQEx?sHR z6yN)FZhmyZ5%^(pv&=8dXRup7li?>hdlLHmnDDVM?-c7G>qYUpR{u)sd7nXYk&H9w zNxII-LEtZhuYjA@&+3o5-pRpQ-}Vy@1AnQW!KsQg$$@2x&nx7a+@t00*K}@Qq8B~MaG8-|A_7aedp{RH^~W@SNY`ad{LpBH+l^r7bNhA4_r#|`-%+ahLz+LL`BlxYYyOD# z<77OpY~>_B`B$WC4SOXSkB+yL`eC2(EmGeZJ>RP7C(u6p6v=IpHoZByRP$+kP7G>%8aDf^II z%=c6#_a?kA8S6pcVIVr=cFR9d_>F*nQa`Q|%l%<3=b>Iic3t^H`$bV1p+CkPexm@v4 z`AU+v*Hh7pl1nj9z!N`)PyV|;t@NV$QXXDT@p#Jhbg%wGz=QlMKoebGw#)5%P$k=U zWq9*>S*P-|kTI+eVrwzu1rER=ZZ+*-EXhnX=K(jswdI@<5H!ONt=+4n-GGm|xCQXD zy?)92-DvzG*Z;}>j)op*cCb@>mEbS7-%#aK*)4IjkFJC` z6Fu#Gg89SfoCEK(OYT&BYwSms?@*XO#P0DjITEA1_(^2niqS7VtE2A)FNt1=-<5Ok zm-&?OVFd39FX-Mkl20pQc`tZN@1>pl^}XTkFAQ%%3XHcU+1ET$min%6Uo(H-pz>43 zTQncOrzHEl0V2W|z=idKo_DFfg88!llI*Fiv8?d4`&Kq|hcrH$#tN{JgfIo)ciRxu`{=@Xi zCHlT0-^Cx4)yIL9KwRRdVLx~*dVK|~ndr1y9%4Gv^A|n)82yCv7e)J-`TRu>WCigT z{Mc^lIn3%$(LO4T(EDz|1K&54^O{(9IsYd6qTa3g1IrQYi>eEMXwq|qza@J9NX~7z z@}HECr7mSZQ#c=$tdD&^q=%>;nAka!tRI3PP2<;iP|s6_A3#kWcVg#R?0iedb1glO zo80%qQeXT6sxOhAEPiFOo@kz*z`E85*nki5SJj`BDT^BdA9g`6v=M>UePJZ}jGmLh z8?l!M^}XryOEhhGsCb3)8Tj?Ww=&FpJiyQ3W^n8XzusPhUvB^ELdDO9{x^^HxwDjy z**P(X8~O#`#BRp$SS|^@5k4@S$E7!u4=Mbr!1LsbyiTaz+0Vfngb1z%pTTf`O6;tK zAuVvz^NYRkZ>$mA<}2Lh3ja^;)%ce4hWDaJFI4`N_ZOKwQ#~LjryS0Ivb~RaO~~%4 zQ+pK0&e}c3|DCG}-*Q#pBj^E;7CXznBT%i#cLaJ$O9@|l4>36DKEJRJu>F3&-ZvY+ zfea`9bF9^0yZcT1%nuy)`)M4f$5)vS!TtgtB{<+*UrPL;^*n9c^NI$?2LA`#84n|p zYXR?}Z7|=VeeeQrrT2=Jcj9MERgb8PzW^f=dV?I-dcx0fKdj&_){p6~YdeY0fA*ej zcq{&Zz~}RN*-(^Hzb$wU`Y#OPF5`P=I>M_9n-r zn(*4ozG8wu!vp+=$D6;`CxzX@@;M9dqVc!HZztKV@4ZXt!|eLJy#u{e@CbH1$EUzg zJK9~V>z{he=oI|{kAlA%l#pyi>#qE4vgiLucK%(T5BX==TV=Jk)XwVtyU|~Fc2@6S z49;R_^+Db${Z7gE4V}r+JUxq@RZi{&-CRy|C3uDJM;N}bf20pxyFmB={secX z@?EHUbZ$iC9+<7#IXOEB(`J6&_cooU`n_8CzP-=#IkF#TvHcjMKjQbPTf};BRrn9y zY`}U_)i2`lwb=Qw#@!Hq`!Lnla;8V|ekQ!neb==QFp+f$-*xrE|69K6%63@aYXjc# z{>#3DtbWUPT?_26uD?Go`m5PB#6QUIr+RCm_aZ0PW$pOndgzgtp`6;`Yb-BrP*XH=%i5uk%v>YYRP^UsmmLT2Pi-djQ|CAAPFiRo|`I`I>)Z zvi2|ZeKFHM@hu_+{J@`IL{I!d{O~7Ij^_=3HT)6GPwD*t={GUb@JCR3Ii*w9djKTV zpM!s?y>(Ga(vcg%Uno~EDc@RX`YG0L1LVc9`+;k)TT^JTF2657?2Cimtok(DTP^xx z>e-HI(W@|?CcT4a%=1p!|4QkY#+}@cKWj*-J{<0Y6#pvC&+K^dqlP#VvbdfX`zGk8 zUFxM8mu@+~7xl}0MXogPu3Njxl}@L5_k%o!X`RCPsos9!=k4vPeoSX2mlwOLe_gA; zcI~SEuWG-+u7V$x||E$4J{dM%cj1w>#eTVof;$!^11=0&O-q?SZ*3&qn zE8r9Tx1g%Pf%zh}MBi%wKfTrV@dzHr{es8h_iwRdM-9LI)~t6Q%=>)^uKiPjw^te5 z>>TJgd<;4t(>qiMl571pS^c$)x4+Nht@?+Y|BUjnj{c#5&#G58@iEzd*&e^cK6AqR z0paJF@5h)Oo=o)IhT-PxU6d{YL{HaQw0V*EYTv#=cFa zzpc8M_wzRHF5&vO?7XKze8{@|)8@_oWyL!%HT)uCdcueC6nuUNuSlL(HLd)vi2o$x zoST{UsxWUow=~8359oe+@a3X&0gdqp-=*mjA5gdu|1`Lez8T~c-cAjC4pjK4ew`dp zmUCuMsN|ei7G4~JYMwp3Z7zzh<;DyV&p@= z2b^n2X`Bc8z06be`-UGK?2_ax@NK@xL#sE*`aQ%K+cRsZ-%mT!@Vh&?IPE=wFR)yk z7X18cS1!&vPRG5P`u(iotbR}Fl_WpXtbRZ1$oBgEEQQ-YNxvt&%>sXyfD*8KyA$(NtEw_7A zX4M5>3-x=|>!%?Oz<525UO)Y%=n3YmS0UDwEAlq zKd&vq&+hyY0dLhG(Za7(kI&(8(BtX-z^;6MtH?LPZ`d_~kBxV(zv@M=8tgLOH_rD{ z0-ig$pF#bA&JAfCx)*-uErCVO@qOCZXCV7xfo$Wd1?f*PD!p|1rR? zu5s#A_iya$sv@WVx%Y6>`V0&IA$kw|C)}?+n#4Mpw|G745t7`+ecPMe1za{{M7v4KrN0pO|m|ogA+Oi zf7}>+i|!r4d5Y=y4{!wX0KezZxZp zVgC{0XYBVB{kZTx=2>4-IF$i+@EiC6oAErM`@`HG_%C&IPLcAHHL07(F_^R^_zXv)W0{^JEU$pZM zp>^Meu?c+rl$?*v<%J$J?Z;0fI@Jc+!|+jm`?f>v`_$}&U$oF8mrdLonBsVDurlkt zfvMSaCpge&wg+1so=1_r7eB$Hq+hhleMI`_xIciC(8Iy~8KxT%A8M9|dWi3sAGFFn z4y&5|pefx+^XeD*7%| zeb)ICCEg)+SD7C;_IK8C6TbxAB)9|eTibj5 zqR{u}Y42&f%jR`-`cvC&hL@N}!i#U(_b;&i^Z{+R$=*}4{oEH(|Aj2eqJQ|wy*;0|cI_TvC+)9KS^KhCt)Xq{KkR`dZg`8J0xpJELEk@_A=8P!Ut(J8O zzQ^N_?_+~KjzX>jtu*_W=^xefYHB90pU-(Tf0#rtgsJ$~^t7*XKBY(Jw?0DL#U_2< z{IKVw1b>tI)(p)zeQUm^Gw2u73CJa{qV74-lRTJekl%ib!39HNctraAt0g_WJk}1q zLDx@5Z2j2zDd%?K_0)83Dra)r^OMI1&q>AY9aZJmrVltz?gcYDgTZM|DsJaUV~G)5 zus`3;bfF0@Ca(@Fr?!7z;qpAsM{R9;hMbRLIuf^MY$ZBEtVG~8N;;C@|CQ~<|2JyC zo8&;;e8uZR;?>gfVg50wH^%v0TyG?LEp#FrT3#4YU#V()TU%dIJ;nJ#2SDFs9YBv! z{wVex=VVwIB!{-Uh(B`xsE*mQpYZ|h`qer1(=q#fW5>jjmE?fZWUzp|}AZ%@SGy4CUj_i=ba%vQR!-QXFE zD~aytIZfGzC3{Gn=tdaP`}lgQMf*Qnc0RX7{P8i^BNTcrc0cYH{FYfxfZSmItUyao zH|HQY5FF^1^iL?9ri89gB~U6O7nQyY+G#Y>-9Gxqig>TxaJvm1<27MiR z+%hj?_IDkKfI;7kdcf`V%zv{+6 zJ^q>xKQ?R~%`Y>JS%7Ks7dB=z_O~&ARDWa3Wr+G?pYF2fH_LBc{a|I{t)J*y`^~q1 z>B2dszQ;cL+JD@z>l-g$w`0odKJDbQPhPw*_Dy5rki{o2IhhJ|&pU0wYpN%`bMcZh zx=&hCJ^9S)$@D6?``x{b;URYG~xBpQnBCZ`|b^SYRi5fy!OH4hx_tJ5B!#hbIafFy{YigZvFf7?6-fW^;_rg z{4r28SXvWe`dJ%4_@|$-wgL2_;JE;@2|M#=tqZp|A`O0@jJu4 zf6JA}uN&_DyVrGHG2Hv*b02*32=9*@?)?Q%{^XS5-lP31hkJka-PayH+l z>(7U6fA{arzG1lfU%B!tF#pl{dR<>x)H?vx3s*L-m0FS{;z{}A&VgU?7bZl860S7)nk z44aQWo@3)^cn(YR8!N;&cQt%xfA%$&ne)`u%iU$4zvgutuB~>>KJMZd1?zu#&P{8U z7Qg<;??2OXPiLn0YrkqNpZvS4`>ZoXwCcfM3+5-^50R!ab<0mK9^_Z?e;xnxZ{9C1 z{k3=J6V-=~n4Rib^Xnz9JZAO%e?91!r+)l@#~haZ?3>^CuEo^+o<}cP&;#+V`?S** zoI%3f;&(2oE?Kmox_H66s_%T);ss||g(E+gqe$I~U43!(qpMcj^QG6``}p}Y`p-G| zxTSx-^6|<)7atImwmY&I%$g1K0Ui{C$4;?xk_Jr2IWj~tq&;bws ze&;*?w&e7Wm(F|Yf3JPZ-ETelq+R~{Mc zrp2?~q63yaZ_{Bj?C+tTZ`$XT_s%%;!Le`pMbAwqzBRRO@y~95-@{jYV&+q09@yj7 z>B~1Baqu~(5A~hp>HH5h^*rq--uk=3AtyZZ^XESCk?Y=n_>Q0d$9Lyk`^y`SIQpFL zUHt1OSD$&qApR1@G~;lKVO#cq{n6s-{xPfexb=0Y=iByr)0(03EpJ@@lurA5sOOcp zy>8g``9Fsjhn1nmeD<6DL)B|}&vo?gY<~~+oYZymsYB&k-Vp1V~pJ=gF5 zXeB62eb2Yf{^861@`b9cMp#Pmlk=CdEY%;yP3Cx9v|{#9T1`({xb#fxlj@;#R-eR` zD4o+MoF6!y*JnYWMSYg^iCriz*QQUhd{8}+{lnpJvTLG~a*k$4bgh%p?4M5klzpF! z?)yL+WWUkLVy|+O)n3&n_A2)xvA5Jo*WS-{ySJ#lB|d(lyKFbYeI~9B8;qL<|I82L zSp|Gn;t5?v>@r#1=LQHSBi|jH&Ag7D{!+c4cpvs_dYO&GahxQFGHTc1dr~M4ir9^^ zY1uzc`elmU3!_uNOs`Y?GI0<4x2ZkvNcGE)80+); zo&BjxpP2m3KlZy_ZoB)o@9g>E^L{t?(ht4v#G{L^?0DIfoj-}YQ{8dT&U(XXpK|Yg z-=^DkzwBdQ`|J18SKfTa*=JvPc;?s_&8*TorXS5T!9wT?!K9X}u>Ewl-vfN2j&=ht z=nXxIe$bQ3)05~2J&AtMljsLMiGI+N=m$NCe$bQJr6+wE`XB|z^dJYjeZWf+sn&ThYdV#H!@t$7LI{AU7Q(}*GJK1{-nrffp*#7O_ z@7#Gpr`dvoX5BbH?I4-h&gVIdod8tr{TJ)ku_G|Z$_($SR7t=TARd`an@Gtf2jTydI z*Y%}qOs9@yK7kkM13-7sDCqVY{Oi0u(l^l)e!2vogUX024I=W71f89&-y7T?!vqri zBz~ZChvL2{qDQk*T|8fhA&woTL%^Nk9Nj;~@kR+eT+Z`34v;$>Gfd<9VKlB9j|*v@ z-_S{1X{>&wN4%efTfB{~A0u=N_3nXs#`v}LLOdND{+OOM`QIPQaDJQCulsxM4ZisG zyR_^tyu<2`_S>|+CjTgopYKkN=1ux{wte5UKcsxapDX;ppR4?V`KITxU2X;S-#b$C zk5shWDuQil?U@UTp-#6O{zR%Wu*IIl(!!x`)Q1Gw?KBv$T-%Z70pnb%t z`1fEf->di!loI?=zV!J7zhBS%p6E%MzkgEB1G@RBht{{QBd@CIu%TY;%K+~yj32WT z^1&f09~98V{eZWdhQQma@cli8ui!S+-l6fh=I$^}hlndH_^|M)I=V+Il?QEM{GR64?QQcZRC**NIuCEt&w7481U}IFV=ynBcw$+zzgu!1LzN%sJmG!7_K1E053p{dH1;vI z9y#aOsd$o6JULq4N97IfZ%7Hee{OIdQU2S7=liD#c>mG9_c{{rZV@MQ)bgMD3CTWW z)c-MZ4^8lYevR>UAHag(`FFsv1J4om58q+AmF`KLb`c`Lw~%}f8V=~d^Vlg?XQ;|KLGDn{+S#-fm6gCYZ;cK{Z(m_8wtv! z7t2(+&Y>hz87j-Vw2;RE1k=z z{=61smgtNV{oVg^isd-}CMR+P-uKh{8BOIaE-A;OcUCTws-%0UzBW3iNbqWNQz1;O zb_2}MQLbZu#D>fuE$u$x4#E4c47P{&E;S$NOYyb!FH;=g@t`ErW4Z_IwRrEj8ka*^ z`VK$Q3+_<*ZSTQ%`}b#kg!)7J2;yg;@K-T)o%~(QUmb8{`NPM(t95#>_6a}cuRi65 zDZMIxD10ZrS=$Zm99%~1B6Z9J`sjU6;O&16X_9}50)y}2KEKLN3^$#(O4d~|e}J3F zK|!{w<1Ul~|Dm&ijvZTb#;C2Z)IKPbO*TxCiF&V+Zuw^MRH1T~Lic}V#jW|(TGUykcvV*EC@ zRN4)M?@|Eg$yBWA+|;}IJHUe5!v(AA*U$B3ABgH>e|LXvj`UsUHU)B?$K{rhTydk(+)|cT(t6%!lgZ5z(~^PY&}$g zUw{|>joC;1FChnbHRVPi0NGJeipM5*oUUoXTf^6qw_evx$*-hq_|DeB^i-AN^!jUZ zH5#YCCeV4zU*yfAa?miTU!=mL|0u`z1tCM^ADR!&7rg=ZC$ALz%%%Ehyf#lZt~S5M za-mCg%X*Y^$-vi$ZgenQa?@#j+dXCI7te#gCeQsuq~aCYzZKFykVNiBVEtQ~>~|G# zL;rfEe?db2jgQFY1}H3hYepmkE~;m`u$M8;@djz;r<4GP1doL!#bAyjOJG*AM3bB z*0JVSv|h{i{92Xj@5t4t9jxOX)-z`1bsYyQ8Q<$co%<2GdWP^I6>xcsL()$uyn3+? zsJ>pnpPsnTQ|9&Ryx0YU>v{b;w~XiGab0(HiMN3d;_I$g@*CEj*d>zduGjH-a+lWg zcbkA$E2#aTx^Vq(1h8ei;ha1tas;jdNu0^#wefu+5Sw?@Yu zQlc-{z-K_W@rBYEm21!r)0q!M_*GMSC;SlWTIdYuIjz@9&ge{C=uECh=!}#Doss;E z=2s;jbfzwJM)NCLujPBWo+{PfVRR)gbY)gyCAI5s40@T)xL`TaTl@}Y`amXO`pzqu zyuj@@$P2w>?`x13j6Mk+LVZea@{CW*44?8>Njn9lPsKpes|rG=iVx~{*YJ0cEBYA^ z^LF1c*Ec91h#|kYB0t!Brw3l zLhfG(dpV6aG`z|wUgdJizqp^N(<8hR{^RmFhGRXyQt(RoKqWA|5_up9>Vj8N4tOQ` z8O^UsKJaR#;FacAv|h{if_jz4+fk^~xc!X*$1MZv4mM*VITe|MMR zRYA^ELXe^SLA)w({1bnzj%!n~F8hat8wIZly^2>~6ub)L{U-2h#;e>#idV8e!CnQg zf*OrK_1*}tX2$W#o5}DARsQxM-!NWfMgDF{w^AaHNAh__( zlA(KZ@nd-}b&0_d?NL8n{uX%_F4AW~?GZxXA#X`NA|IueGd@B{5<3*Cy;;74qPaKK zx7646*q)X8cvE`^kFvc*#FtVZ75qm#8V|(U$*foXT<-aC?lf+HS|+6a`Wsn}Euuq+ zlSBRT*K0ptPxTTwcwA6>!u?PN1*jbC0P%V9V7Y3P_e%I~SCkG#pKFqjsu`7wGo7M` zs{UPqoW$SxA}5-BUJW?y-wHXsll>C{>959dnV|B!$w@W!=jqUTu%pl8?nAp6H#oVp z8x{l)T#TONQJ_HeXE!A23NCIVeAsSw0I{#QSvkfxwhNHm%jM42dAm;L4MU)DdU=zh zdSY^f=T^q%%jDEluEyWdg~2orF86Mgi^Tr&+Xzo}9he=VAo8WVM(qiOb^6`KjGwyDX(&JutMZ2th1}BUryrOG}#rh3*vSKr89i5 zD8B0Od3@Q&l-{fgRL+JyV?3Gh+g!1V4CCn+I}rJ5b?%S!2E6K|X5=10$l1)d{8d>y-^q4N|2wjeYWAz9 zb+qMrMu+rQ^ihB92>M2(#}2J;q`wrc6UH;p%YRFKBYmW;kD|iY^p4^6jr1Fg|85_B zBfU=7!{+O;jHke3_Kz9VW3xJbp{xGdY?tVJ*$b2pA1i!V{WC(WOSY5bx&<%e`fU1d z|1$V>xzdT_gbstXG9BJxefDytqpW|XKe>hY_3;S5ES}E`rOz_IC0{p>=-$l`a6m%sU3e~QS2kLN~T<#6Y@Tv zn;^Qta17ah6CKFDp66$b#K8syi0#{FPPO^Uoh9uBdkTNfiyqWs-#)WY+nJUN>AZr! zJ_yB6GIIgdlR3-k&3jT`LF;SM9}cDQWnj;i`4>GubH07Aa(7zo_6RsCdU>#n=hFqt z;(oYh*Y{4>{L`hJcb>{69FGYTBX-}a_*+yS(e}%kneG&TtpxdfR}f#=VcE~6bbkm_)|MZaTk$K_iO$IXdgfJ zv&h_L^PHNe`pS5!AN)Z4YTQfpTubgEr85lI%-14(5x;GVKRwH<@cOz>{hGIu9}M_@ z$m?xYlixP`t>C?^$5li7ZNq}Y=N6jE^~L?W{XX@NHoDS_2`9Rubi}Eh-T}VdG{2>k z;As`@Q`**fDP2w1kan3g^QmavXOCt)m@fNEz)P0ffxnE$20J!&_Yr*?zR zxZS|(6nw?{0v#K)CwPp{{wnx0i+`cn30Qt9J|^-4_W4+TO=DlcqxqW#e?+FIDW<2{ z7u)(6ogZ`-HjRF&hX_6TlYNr~$i^DJ*i{i#aYzZIrjq0eIPoq)8^sT;YSn)06x+$1mgXLEj^?U!X` zpLS?_qw{x($UvI z51`z`>u6(n1M{m{Z5t$A8W8?fdO+~m@jfZJ=kR#FHj_KJ{b+tYoLr3SAAK3tKT6yV z#`yr|8A2xhpUQYyX1qMx@Ul(jxdbA|^q~B>wEvqn(L>t5t#to!{%)z^cggJYCC-1q z=pp*c^_1Gg&RFW>acwHIJcn*PMEK+1QtDHEn@ACR_L7`XlT`32All+Y4S0x|#wp#E_^DA1f<$HzRDy@@_lE7=D z&cm#7pWs_j@Xr-*ReWQA58Onh_*OQ2i18(-^bz!%{gfiR+gXYy5?{W}7df_9 z_$D|(r^r{j4>C>gr@`Np&M-G`9t_9q}gz?+KtZohd5bw4^h)5dQe^Z}%{6j3bV( zVXjyE-^2Jm=tY02QvYj_@ zI|oJW>=kckzU>E#zu^hi4p<|Pr~Ep3w%w~w=3nUGZ)H5-SA;(fCvRyy$FyuGXx!)a z7g;;D&t2MA+q+PoWrf>DX-C!_+Bulp>2B3dhVOqtIT=r+?*oN-Ji$KlL_`oy7p?p) z(hlg4jEDE3{l%@?nO}&F=ltY&@;aV^j;CO9!I1m*{yDAMNh{sS%#?i!cM$gz=Mt%( zx{r?G`b%UyZm9k9k@wAw&X;xn+y%3V-g)bdPAB%ur(-@YMyqS`ME~$Cge=dv;?+w3 zuBLec-Hzy-(!b@B-$bW{pS!%#Y4)d#=wDl)dRa#4p8Aiib|KUEK|0O-c2&U_^sA9h zqd(h>K8Hk~gWnl_&TBvOdFF@8hw>m3wBA9-YB9bW<^}JX^pPU_75cXUj-WO97YQB2 zI2dpJwUHdzEBv@gj(m{uC0&sBn+m<6KS6%`Ho#%~03gRzS`Qs&pLZAYccJ*Jpuc_G z-^b)!Zj+qR={a6EE#wT$hwifl3Kz9ghXtV@#xJUp_HQlnc+n%V%TGXB*5&QoubTQb zb`<4%L=oZqK3l(jH}mt5_hCw?NBuV9e$ctybv`1$!LrZms9xS6@+k0>+8y*)-N5)3 z`KwAf$fJ^gxE%X)4b;B_>seL&LXr#W2$-`ExXD51X7 z@T8Cz)Gx2y# zV-bi;?Yp+$3Z40TyY9!_!F(3{fWMol_&->`(bu@$yx3;#t3G7=GiB97igz&Iza`qA zxl`~s>CXy3Wx-?K@4(6uJT770sJ|Tnk2i-k=iz^+`M~FTZ2eh(!(cVTHEi0qxkTiN zVol{$Ii#oPjG$K<1;$}Md_koMY4o_I>e6Uj#`x8&a1vYlM4oinT*(F*`Kyd$J#kJ*-l>DDQY{BJaOxg?JR56PKNI_13+1B$z5vgWEBr` zdkehs*Xq;szbmC3v-jZETUx&tv}nitWEe7M@$Gx*uK3OcT&{E~p*$6B=K((f0& zDUx3f=KeOxFIhY1!Tl#N2z_J!KayX%V(T^O_ZRAUbyOeIOWIp4eeO_Nh_9HR*r2v0X zro%!90Yv4?`-*-W0Nt6czGqp8!7= zK9Mqih~&8>%})g@wf`3je^Whst?q|z3RVgqNWGN#)H>dWB|X{r=*t+-(K!g`YL z+I+PG#r^z^?$A2!V18GYeX44Ejqt-?)3!nAN9Ji6kN>2O=Sdll@NtwQ`wy5P1h=Ww zU&nT1wkt9nDVrUL^#bt!veFTj$09o7{40h2`(M!KT7BND&$;}}f_@5IbYI$E%K4$d z$6qbyxt*@N{2QZny)s5;LtoZ`>W72t3>RAwovZpak&}xu@3>~D3bG&+9Dx zYF%f#j@_%d9M1g{p5)&mbZFbfX<+*sIv9`B5Uf);w~W&OyaoIk&UaUsu0`j&lW`jC ze0MTVgPreA#%Zwg-Jam@M)ePv<@GQ=Z;bY*?Y`*_!Q-^_3juyY^bhNT`_-UtB>g<_(h+_0z@POh|(#{%sQV#wa%okgolZ)(@*)teUPT>>5OX3HB z?5cfT`H)M^qxFE_&fI^xHx29FTkmVTjECT=pw~oqs@8At6I#D6|5AAt-mK47b}F{J z2OE?>yX+ZM-v2J>JLC)eUCFBxjg2a$6ZspQWrXH`6G?rd3hP9 z6FU@YR5>8o-$JPU{UY~wO`^ZTcZmPx2@lb3oX^0`llfJc7q~=rFz`>p2bce~z$q?= z_$rV2x0lEdj(+zud|XNW^-6lKs4RTmmBcO|mSw-vEA>(R{%Whk9AndfqKI}e401-&VMn(+hR>tvc%{~CVw zieED1Hq)2yv_->*TWD;v^*Oh)O z=@Hsp(xTm@JW2!v<8*#SYlKmJt$*W#u6G<)G%nb)AM^LjnwMX$D74l3yy;O9J^Uw>o1i~T&O6}mW1HD2)d z&i@G2o4?uGEe4{0!;zBbx9J@IY240glPeS-`|bPSD*isL^w-%q3s>{{bA_wLFG=j% zo-;r4;3~}*f8Rm>i3_El-jUKD?RUgSVJ8r|hV4Rm*g6G2!O{gkC&)86N}uKjFZEXA zcA@+?Y~DQeXEZo@dx)I|)CahT-*oQRG_OMN_cx|0qThIXYClyjguH?Gn;XB&^ay{I z*iUeZvq$OPX8A1^X9+zszK-2CquX)0Az`0*0r>iwt<2ZEbR95XFTmbLd>wQ=#-Ci0 zzicY>#rVk`kpE!oYvL!ipBt*)QY}?^-tPim?j&M}od@u3{8j-ku$Wbz>}cb1&i$jT zc2f4ED$FoEMN!7n!f)i6`nEnzUKf5=6+Vo5H`4kmpsh}(!)3L5yRxhk_-8c8`32|@ zvL39Rl!zu`VMli_@_?`*uN6xm2PEEr}qWGLfS3*ak}+Yiy!iG z!86=wKyW)KqFe081*u-YPc0R?<<|uta(W&sw@~O-ZlTa8(JwrQ{;#0!r{-z?JSivk zBD@fN!Ury$Ap3GC6ZWW-@3qB<1R%Fjc zKMp~!suBH~!SyuLui4U`dwzOt(PMrFI#OZzaFu80XDbX}@qZ@xF&t2?D&>av!;a`# z4R9j-QF*;#y$?&*+@QR!=lA|)IZAo`9>I?!Jp|nUwdHm2d+&vy^L<-L z=YL}B+UW0Q()mYiT_@>0E`4$*W9G!0&r%!swc^eP> z>QdlKukB}`9Pp=fx7Mfc%kbj^ezhT@vU9J%|0tH3!21_`Zf4ur&DCCzu$z~SzPGfS zE5H0Vi*Jlm+e$Y!FTOFfZKWUEC%$p{`@+|tMKC^=jsBgkX~93|JqbCBX_pesFnI!j7xelh+S=F;MA?sIz{Zs$R5&PG4&Q16C>)ej>H}~Aw z0r7KVX&VQ}=N|33vC2D@Zik=~B(H3MU7!=&Sm5s-j~nN_9tRxyQe7k;Z~6IU-4E1r zhh_K&byNL%e~^EVU*&n8m16zNkIpa4c{a%7J1hS`O6D2(L--|erf6SF4lUQd?2x(`{R6#+mj2^i|92x^BF&y?)zCp{U|=G>v5XT_s#k1 zb9TQA@3WL3UP--yC-|S&`kLaG???5Kn_EA_UHgT$<$F)$lC=F}&MlY3zUlRO8^o^Uji>&518GfX_o6gTfot05 zG;S@?bWYM$O9zq;EuEKi&C&%)*DYO?bg!jLoK96Oo#r&wt)55CNc?|`%jJBbGh}am zEe#j`UHpW+$xqn_!Td_x1v}5DdLQgsydJ#1GTYHm0o&odUaSJbyRyIse|?waQIE89 z%8&dlHNU3$&Yi*K!YF+{r>jx=qnvh8TIAt@DE(D>k9BDM`8ub2qx3vZ*Q4}NoUTRb z*_;le^q!oqO4|QMn%6yA;C!zSX&MixGnMo22Y!Ga+I}1GK>88NJx)z>Q5eZZ9^n_> z$Mx-+og?=;1Rr6)g3QuX&z8zpj@JQJ9|WMO_*do=lP2kHCI^`xTQ$Xe0?jcV`VYk6 z>*{HJuj{bpaoT?{D#vg~za{^hiTt|fbbdJgfd7!}ciQ~8GRhO*?e%7Ixls18o8TLI zgaP=aHp_n7RL>UAlga}!KhDio{EX~FvQN+UA>gC`Eztc=>aXVSl(F@&7vuA6jJpwU zza7V0zu7mP7sEYR2&Z+D8>yS4f+m)9JKSn%Ukv|~+Gykcp zKM2V;G`?*vFZ*584)oue@MXV8(w?N-RIY0Sd{3k4fZk30();{|@H44-ka-C|nZGGM zI=c^~4d1hz=gH!+g=W)PJIUAwcv`q@T5c=5|8f0KSDHheDvDe!t-O zmNI-ZIneM;(pxF+Mucx8+&@#E(}Zv4KQS!6l|z%?wvT?+Hl6Vc{7&|R{6{0cA^bj* zuJSrn`LBtNhr$nV-k9&>v-8GwpR&JR?$awncJ5|5b#!qg$|o6I9PcZXm)Q5vFCa%Y z+^4Mj4_kbn@>u2rHMvhO>VF&4VcK_#$uZIT3_Zp_tOD`(@=%`WnafUKdbG)(DSXn& z`BB8*sddtOqf=?qZ{z-tE`5^9+k11rLba3BphrT_Mg6LG>N)DV>mokT26*YZ?2>i4 zz2e9QHKF?&Uv*Z#F6rD#(aUpZ3jKAj7QHMlaPzh>j;za_DfD0LqvbV*CwYZ$aFMh( zeEit*R}9|*y9bKxt%LM_8^w+7@MjWy+InOi0^gXAM{-%{@%th0Y#YVn*ZUvZ#NGn= zBUHTZ6n!*-cQ+;R4xatY56mwge3aJ3_P<|5?fZ`PaB|Dxer@^vA`%}QE+>S?1TLX( zn-`Ct1iyZ!i{h`0B-b~HchC2Jk(O~pYK(Wc|BK=s_HW|!?%x#ej^0AN0}tJvc-IcQ zl}B=96TK692zpmz`(da)>j+H@Ex!NqP>+$$gpRB1Zgt%kNK;I8?-diEln-__cZQ z%-eop_*F1JF|KcrZ$97qlMqMz+Zd;vBLq5BNv`R@NjQwZ{mJc&UlILE#8py#paQ+_ zKlp`tf%s-<=bxAJhYtHCfFXWz?_D*=>jr7o``X{a z_z7kOIza8X^yj1=i(|0B;tMbxZa=-Ly;n>55!*YK=uG=5vL4XR-m!KffBp1H;#ZiR zseaKC$81`9CargWqwYt#>`D6l-okIwN)P$G9Ec+b1;zt=W_;c}7e6;w#CK%p3w1vv zDF;>4s_&=w6unFLt?XPwn3Z`6^*nr4_?mw!?1yrHjm7bj_*N6t4(yzatEO={JG36> z#NN<{b}?S~bxOM;FF{F{aUnl;kL{SxMs!B)fxg-U?YwA{{aNIx=ss#G2fLEwNB2=n z{^0piyN^0~zSQocZe@R#bI3RjT4Q+Q`X;eID}Tj(Pa(~3^L<10g#B6M(Ui1<@I`7L z_7|!BfzLO#-(z?h=z25z^YC`(_I{-|7MG9q7iIl$y@P!1`P!XZ+((V$myLGkF|dbt zpns6hdq_SRq(8#{fVX@e-wocECGn9r<$2$q@dfK{@V=~;_jO#Z_O3UJz02-XF!{U% zf7=w{oyt!d-}|30-u?ATFD@2(0n$qAYb)KSP#I_Vwt4p{RN9TdZL9kfvX`s;w35%q zMe>u_1IGZ}wkv*BhsUombN_>1gs+viZ6SW0u$}SiB*8EIyG{I&Dgz3a$R8X#L3;$vM8PQqKCHFr=)>pxomEW*jRJqLNCwq+08PrSr zpkw@+_$yf)`$fLQ@rvTuvs_fc9&117yqlI)e4Adx?kOUZe>_^J0E14=472 zYFdxUZOQW<;Xj2ROSGTos_80^a~urRByT!*0P|Vrc;Bn#e%=ptKkr6(%Br_Z5~866a!)_sS*r11rBWycpenVC82KUh#flCEDMAVfF(d2i4Ph<*=tqq?~Tj-5^r`4^fr(Bt@#J8UIhkzk2`jxQ!8=S{didf@i3&SNJkajDQo&j24$Z!Iv$rYfHYoM)5}YVwi}N zjC~xQmuf4%T?g%`O+2_XoA)=Qd^IaUT zr+If#+~X$xydln?lW~uYKPTfJ8-Gs5JvRQ_ru=!E$2}JQY;k_w&~cB)zQ*X?=EXf8 zyN}}kVEq=yWVCUQ75`erJsu^$#`QhCe$-DCW+1bt^v2giwpZ~lNk@+tI$BE5QM)%G z(;;-IiH@$1(a})%)9Jo-UT1g5_6f!=GB}rYpP=mxjDK!)_a=<(R(j&>yprt?+-vt4 z@IJ@bvzpo&?Y#-_AbL1BCNbFB9!2;afL7c$%~Z`ICHyEj4F`J%OBt+72%`5QSJXOr*S-_-^7*}UkClqdt&RR+5RH_0+HW&Fpt{x`r2xO z_x?{B`L>(ZUKja6;=03+peFn||A@*Dk0kkU2tJJ8UnC!V3;0F%Gu4GZGoQo!6#5wN z#NRQJAKC(wAI@v&2k`~o$7_-!O8F|wKUKfZa2mWnLHjxE{Ru^@7tbrCzj^KNDDSzk zeK(aW#?Mx|TC{jia?T2JNuB0p{7EwZkR#&r6t>!rDDr-Ue;e=(@Ed=o%AHC#o8(TT z=V5j=&u6InYJ+m;2|_Q633_SrLrZ#j^YHXC9v6Nb90FxM;t!tZ;t}#3U0nEaSjL^a zo|E+Q;TXLfw_{T~qm2u{nC5$&o&$iLa9Dad?xog_(f=qe{10VZ5xq>tg=e}vt|J;x zvYi;c90yyJz|H7Ou@$}4xbOkvJ@A+5%edWJwqtSOnU0MEp2piTI#yIV7ST(M3!i8Q zx`ob9Vm!XZxs!I_pC|1k>9WPsYq`J9ba~vtE!wel=a$Ct;Y4fC;Ai71jEX*wo7`WBfU}dy?voKUJ82iO~o(O8=Knk z!7q2^?|YSgGrx}f3J^D(=?(l)c7m@x_^9J@8S$@!pFt1XD^hlUiAtr*KP?{(plXc287>&7z(x8SCjB0RW!itWo9MlGYtf%8(Z(h_ zKkhH&ajbznCF8L8@p9iu6n`+Qb`yF z+eb-1m46S?HT6rcnqRuYe{=7_aC$FeO6_N^c%8sstDVxeouzmMi! z@8wGCeHojO58naMS-H2VL+_<^ZUxb^@kZ}!pj-Q)n8v@e{3bd!f}D?b)LxP3ugN=u z@!+o#I+p0StVb{r)t8d?%7V^UnCE$Pw$CR1Pe3leiC8DGByPx?$wKTwRaHW3H-`Y41g}=t`MGiJ9-Ql>Ium>pJDH;8IR_Ln8+5Vcc z(NET&P~PsDV0(W=kJ`!uvfhI4NW1ME-vavL(`*L>z3iZMS4A3hiM|v1nR-U`?hVp! zw~Mq7P%!;8yE4bG$0X~x*I?YlS6un!I-ZxycPafYWps1{x9?JBkLG(wqJHO6m2?f& z$h<*WlKK&sfa2z3vRsPiqmG`V-#aYVe)~cP;CC-@soT0i`-F$M_r8iJ#xL)KR8{UH zmU38klAqChU3ZWl>I|Qh=2x^{%lA@Msu%v_RcgoI*j8sa%nEqk{f)Z5+Ddhvk2cpQ z^c3?+-+9q}#C-`n#fF5RVqaz!jb7Suk-fqvylmh%a{t@-A1htWqMcpwU3BkTsUZ09 zjgsE~_Cb74zu(e)jfdU4pYS58Kd`u-ak^^s&&m72`x#1Ar6ZT}db}m5Ydmp@XPj0) z3dB8UKM+5tdcQ||&&hn!a;r|J_AzhN?+%m$oObw#(pPO4bfgx8R~D#9<-=C{4kO-s zta8#w=Zq5ahS*6nXj$y^)lA6qYVtsW|4+bJX`Kz85BVqM|F{R9;hEV={2zQv@G#E* zCq(?;;)2<^UO3DW9=Iu<;Avj(l>ycOPba)w@igOUy@;WBCp9Pr40#iy`N{#thcsT7@eLQn%iDqZM5o9B#Ak?~yQ-&rKmNS~A4&DNG)hSPu^quLE=Ee@ zW=;ejJCD9EGapN#mI~j)m-4FV8uFd=gZ?1CHxc{WJTIa6zheBAm>d*>yAeMMU6tj< zw8~MNvMQIMWz^#%E@rKhYK7>+{43Qdave+$H;Ga(^KFT(rKz3e_8!zmptj`i9z(I6gCpS&|>c z_3RZo5@h>WUJ$#2gFeG>@=>1TLXyK?#q%I?nw^Vr*V1`307>OHxSdJ)lALe0{diZF zw8e39cgi`oGW15S-~X75GfVzG5W1y4=o?+!Khw9$?EYY#Pm_OiRO{*GWF;~y+`SZDa26)5cK3MOe^(78RsD3o7bzgK}(Vr$mHllR`e2wclq2H(Y zS|fao^e_j7j`~-%^JaO$Q^=9k{TlBlw0l64=f^bP@HbUvc*9<~QSdx*UUE~L+CTio zEEnF%_E2=dv7OeXeR&VNsoHZFv0T^@NdK|l#Opxst(rU=ON-XE>MAeSEmseFjRuy z;QoIc->@$|tX^&Kc1?OU-;1)tJ;qNM<+zX!bTrjzX2Tn zQdO3(vvyt!bQ)`7;?s&x|03g@WO`exaaM*N=cK>tIP1U*z-!X(5uH@I5ROlP$6#Df z<(C!gi(tdk=rbuJLF5XJ1$T@UZZ&=b)rge>F{OUYrKaYI$F?4=e|zz z;6&J0J)tMEKE@>QX2((=ua~;6C#;iBDj%}k2w-7hO~pJV<=0$6=x~_oWjGt$0XCvf z{%v;eX&~>Nll$m5rTQ2S9f^EfzsdfQKDi$$gAt!Qg~w%f){ytxsh`XT(a%~;&d%xn zM;Nf&=t7Neuqlv!(1zPk@^rNz<(<2j2f=})poF@80bcyH}(NApGGF>hK@0dTy zz10BU`GU`~4;`g#-!YO0wVde%aL|zZ_%z?>u=}Ov2U4%h6V`1>M z;Dam=qr)Zcm+o^#aT7DqeaLmjyOiCBtovSJxvKNMf%!|c52ocd1-(4KO85Ocbw9SM z-#Pa|hF@CC4elpjD*9AO>UZu^E|(HKnS`+bk3kGXua3^Gs{Lb!vCe&l#*6(clxrHd z3j^zSo%?O`Z~eR@kbSCZ!P?h(2kL?D;HUbP#?jRHIsRREFL7Bk-VNhR9_2+Y>nPXc z`>&_;enqywOzJYagLTC8E_hU)m?2I>KNY&7emI3*?Z<9}uageZb?Q>zmGU?kjr9)j zSX?&tPnmRLvK@anZSPQhn%{)pHyX8bJhwA9YG>y}I~K=8+BwDAfn7lQKSQ3m!}Mw8 zmvTR=nNY{W{yCFQ(sqiOO1ehl@T5JxhtJ=LbNFzaKWm_pVM_X6*+VvBMNR zY4^fF^s>S!s+S4;;HS_JJi(98YfZYqzRw)P->0)ZLa%*|L!3O%F6So(&$DNak@M_& z4s-Ba+H4+gN#!;SjMD|7lb{D$KL{YrE9AIt?w7$atM|P6mnmEgFT@{y5RNnWd+>@* z{@xoa<+VMNvy$z7PTNB`0j66~eNZiA9`&BboW>!D;@9Xr!k)Ju!*OSBA3ohfwr~5n z$@ag}a=dRG)p&1c(VohAP4K;|WqWEDi`L!H@V%u)d$?X+=)Sjw_gDfS2#&D-gm{`9 z7M-ip`*JAFcndo*)3E^ZJors3{l+OjHaMW1$WN<=zsGWl?YA3WZcoMK>V(`tawpHb z@S_)AZbL|b$vpo5A~#I_qs%wP!*WlwoUHQTymgDaz)zp9BKBnuT#tE(J`4#8B!TGLa_g0oT9m#oRqvveD znS8X-r#7R%-x4~Qe@dTrzWWNUr`k3k{dNCC^mp>%MxTlgNqJY~a^^4RO4c6By_37G zJ&arE{T=))4J5$@gb@kR1@0-l#xdnVWU>liM%eDBckjO48c7@o^@d|qK@y5{gp zOS|Q~(D{wUl_Cd~R;nD77W~TpH?=eQCg-nyv0xV)8V(cZU{?Paw+e=lv{)~CuJiT1wHqCM{+! zZSyrceQnBd+TK>u*QQ`!LGUusehYnV%EFf89hJT|rPiXo7W&$hvs$({Dt&Fr;uh`w zf2OZB>2-a!|KW{gIkivrx$5wPcl%AbVM;Fy;O*c7s#iMrr^tFTedub<@!;Uy8->$6ZL9*x`ur!u59%G zJcl1jqZ_ebhz>;=S|&;-|9I-j&w(Fp{*UUV-?{v6>wm7kIQ!96EAIKyYwvyh{2Be{ z9DLl;KVSKH<%pk_%YT|MW7@(ZI-l%6$o0rSZSk)9NJU$Bjo&0cPZmEnSs;e3p>6#;l9GVK7Q@Z7tTBXx*c!4{IDA! zs}B8>I-i~P^-ar8UwX@`Q~%m=!l4`c2Ts20&|6+{^;aIxUVHXOzQ6OwPFVb<_nh$i z)Uq9}caKioXU-efTwH(OpYE@{cGsv-0;WGxj%pLH{ z%^P2OW95M2vAh3t(%oOUcHq759J+P#9^>EJCT}5h_4{-8e#r;1@32|F*!r#4hyQlv z(}&zYe2J#Fa~gC0^x4zbG^R)Uw~p`ALzE92^Nu+9oYNcAqxgG9W0{H9&zaDe9>w4E zecj4wKU+NpTD{QxzrOdZb3a=+Z}(69_gSARUDp2b@%#Sppq^76TXN?mnVq`UzNB9A zoVVgf4?fg?_y2z6i@W~epo9Lr?5}@uv(DY8`|bP8{nVd-_>bxy+LH`FQ|$LCG`p4| zzhCFgO^4X;hH~llNWY1xxrZn6p)q|DxpjO>d^2<*+jET{cdERbaWi=z?rkQ=s2|Dl zc)s^$K9A?T;~eWz4(0nvUx7bN8vQ`0$iELp9`s=<{)InHLq4+xKR(oC0{L{IaKwgWD&For?b<=l5e=-US{e0_nQg-@OYZ zjVR4tW+w6>U1WBkC;m`)(0A|;=Xu7zmCONnS;f@Q^H}}*RSU;^`wzc6IV!h7#*CcslPb@;%aAZ@nn(;4P=A8+4yG@QwXCyt$s}>yQUAV&|Qx za$yR*Y`^SL*@IAVgIt*YLsBl(adn$MhV~%m+4#M;YJU_y>eueva)H-~{dt@Ahh!h> z&ur~aElumm!#HMQ-OzgCb%fqj$)6MRA@XZo#~*5Y1dm>MX3Kuq-S|N-L5;?X<)wZ+N%)O= zpSP3kZH8~XIxY-_^L3xDH6E&8RH*)rF4L*{(|8^Srk}?P_pK1)j_BCgN+&(n#p?j+ zexf&_@)Pq*aoKLR`@+qO=fU=aqj^YG1)k~Y0{=`L{w{SO_vcB2UoOq|VK^eTz~7g0 zzX!BGM861sBYKiPKZmOgkoP#AE5aMNV&A#t1I^!2dOT# zFN;4!UDlsB597w(wDA?M7ip|TN_R56xE#@0=8xV=Y@KDX~Aj)d3C`NW6J?}h!c zylP&)>x;f)yT3OeX~3b@I1jFfA%aeLVWC&y!1biu2<2;u^3@T_;1X)wI=7%GeneS{^9iCL-{Zmi27qt8-RPXY-VYUKR|}kVJjwa6^HL%E zbz}zX*Z7MycqTC26+ell`LyqA{5|n$Id5h08|#Vho8-a5T885^-|&I=b;!M)^1THT z|H4}Uz5qV%Ehq^;cWw9Txv6`FUIMgS-siXz-U2tkc;|eJmm%~B^TYJgJ<4${F~7ZB z&gMb;;aw!{xmmhCEgfo{ROc^1dtipqc=fzmyJL8H?MAl+|E+${J)NN2HI_G0m|Cn0 z+;Da+(-r6Iex>Yl8687`0*%-ExY1=9H{7uQ!f*I@g6A$IPg45 z945@;2ARjK--mqsL@tWv@j+f6n8(%ed6f1cS9FQ|Cw@y&IxH*Q_D|IDKP>HIAAUcy z;yvRCJ)n4DG;bSh-aH;BhVv6C@AvC7u^$-86<^`+@3(pL z?$mZf-tbW$;ZJX|BJ0&jd_<8i2v0rYE8g2(B?c#(-(FAV+vGWlcR+A(r3pN)nx$o3 zy0A_7+}m}X8GiR>2lzXqUv*6*R088yElLZ%g^6@Ekp^P(cP?xG4L_55fEPqxtKy&F z4H#V7WZyc}dkCu8mDC?^A=XJJjYsKP6?n$ub9_&Sx5%xG!6nr5gP~);SHMm1(8f6+ z^*S3T;;C@I>Y6|3KP>yYHSNdAiGK8HdFL+Y?|Y-Pz{ln%;pdr}D0HkzZ`o7lsfFHx z@k^Xxi=P#pe^o!y$gjfu;_~9h8QOigr5=UbC#b*P@k%GdC@z%j`&WG{HT|$opi+ zV7{q-wQ2MdbYXDb^nES*PUQ5szeWafP1S?Gkbb+#2N{X49nDi(=FQI6V?ude6aJPE z!zKQ8sQ2ttbG-yd?=*0RD4CeHsuciw-*F8Ehm2N2li{b*A@8pRb( z#$i94$9toVSI?sh0|H&xL@&LR;biSkU>v@2-_b43NbiR}0 zzn#bbp!Cx_-sGv=jXIAxZf{zS@x!}F>BAiDZ(l^mwZDby+1eYJO|YALq!i9dvQrrMc_o;TW=)t;Qjnije%_EU_5?fJkGp>Ju= zr592zzQ-<5`at|C=aa-Pjn-0PuddQQ5A3MSzkzRhj>VUAETP$pGDj19(_PkXWFJ=l z&7tkX>BEgb!C*r3b)tQ*`yT16jvMS?QA?vboT=`;C#HA=I52>6R09cJviy{H^k zg~HG6!Re)O{nE2~TFHrhT3_g7Kh(*+o*b2r$7A#3_*|720{f8fy$kuB7^l6H23FB=}(>MH~RN3bd=XA_FG;_#by09(W_9}Z#6%tMfk^A!XKEv@7l00igl6d zK`Gnss}4;!+%!MreXD9NWcv`#6B2#MK&<0=bb%-PAGUrk*8Qs}&gF4DPAubo85h&{ z%w+pMC;RGlZXEkCLf-?4OXPw@vQHv$-#rJuzJlPGTd4l|3w596LgJVBEdV|MsQ~C{ z_#Zg^VZQDm=clR~35bhV&p#yq(6(E=N+G6{uYsX&%g8ounr=Kd#Jn8qkvsOqYzFkQ7Vn_H3Yn%-f#K zKjDAG`6nmx^<27V`rVvJKAO(osXvi-YT~>3m{-O>;m@#>#p$ETEs95o!y$e;=cKB1 z&d__0>RV3b7Uj3zdjS{B8A>q!GXCrOl;6EC`cC+p;TQA=`1_!fO79c+AMv?qqQ80P zW<%LO$?*F2>fmqGZfN_*{y|FbmJ(fSq$?r8*Y=0ZzT&wa=8s4-zJ+REahbi;-oX1i znLiu;L6ewF^@1Lf+@A7ltcN)qh~WH1zMON*@jYQE!S{J&{-E{a{y4%Pua=w1{v|HE zgucr>XZ0&zb~qrw^|e?Ze~+(^Y{b{S1Ygc6U(PANHU5kFBY!jpy19bJ<8=#P_PT{H zNAX6*USM+EfU@}$u;3-%46q=+*7A|_oHqBHSqM#2i+ukG@J1S z8R~b1^~3!^2Ei*Uer$vC(N^Ze+1(WG#)r3d{re>9uSNb)q1YQTW^cgCFxH#&o^bZ$WY(h*rP>J^W%PQl7GXbD{gyc!J=O-zwXtCbq=Ju z^79^8cglp+Yd*O0mtB{9pt&^#zxzm z82C+f`yc+gM_+a1)XwX#Jz&R)J@?#Rer(=P9-8^46L(l}&5FK< z+u!)x2UyxohuJV7>g}c{^ON>_b;g-TQOre`m&b7A-se zw4;uB&(RP6aPi;9{m*XIy_J^;R#I$q`VrXF-V&^HwP2K!_7E#9|rRxab|ISo(ZhdT8~|B=Vz zE5RSylkp7w{WP)b1-GgEkMYLf0=b^#;ox@cgTbEez&rWLCyDO zAPrfU@S&5&cc+Ixkaob|DR@8*r}u4dHh5#zK<_HQM$%>2Z5clXL_bS@?^VTb)(!e! z9GTgX>X&-l{Wh;4^JoubvqJishxYpSkNk`aD9`6n+;eIdl>3}u#KFX1ybj-V(>R}l zesLF`gvZp*a~Kb=Q}t_6dkNM%ua{t*oj>rZf4u zr}0R2pCb5%%y$;D`ey*2{OM>UynlDNJyq_x^%aTrmhIG(8RV1CgP2o63(y6m0P2=)toPX2&u?m;})q1c-sh;2; zlJ@nx;8{sWaA`Fkukw^G3mpjVz`ioue?SMv@Ob&&)l#p{-vZzxtuyRv(mD&9x(e5^h;jVqIDSDpIBcMmz(hZL0Vtbjt_f4>vdKJUNC%qEaOeR zMd7ni(&-ZvK95T}x3l8SfTTx+&jOG8Q@UH*`I&r|TB7azT+-=ZX*<7?bO1AWH_gkh zB^|Z1W%x;S(Q{K#-yLzfo}|MKbYzc4`dbDc>qmd`yLg7jgZqrYDNtQWSMi?a7yP1e zAF|c_K?>MGsncwKVeqYk1y3Xql zBpuBwzHg>?sQ=|!|Bq2WuNzrs`74{(Nn`sKm)5D>IOxZ^=tTdG56n^i@byH#+Kb&w zWF1-g;O;p6isIW>6Xjabwe<zvSusCenqUDxMT19pTCRxI9kccvtvRLWCgE~)RbzWYk z^D<7-bgYi>I6r<1=P46AcTe+_qICy87Rieps6P=$fZ#WDx%j-3UN8h-Y@oaMdqxk0 zewchx0N&q(uR-3KPyV1qxqPDB_ga(-66MgXMtUBQ9z%HlT7{>Rbg@Izq<`Ukv<^O> zSO=i@=^pAX=$i0RhEF0cLj+&q8x7?NPD8_Ud8{1ilTkl-UXpPc4rTuWD0?VJ^X6Ne zZ9d-&sYmoT)DzlxbbhO#6Ce`Yk1X5-s_qp?Wid|3CNyp-7hBg{T-Sex13iDyu3&@I zE{apS9qE2bNAqla0`!^r1n7tG3BCuS{Rdh;;>U5i-2OLx&vF8{BXn8qB$rl`fCR+)B1?yaI@3of!Clp_{HorE|nBF2(1}oAj}o?azBLzm2AP`l5Pxzd6#sM<>Tdc!hxp9ej9O&x_h6 zBED^KOzo+5T$Ni@j{o5{?hnFL==_U)GZgwvY|Zo$@}Fp$pU~z94?t%Kla`P&W@_eAHVgsdeuwP z*+@cYx;klfP=TTyYE%Kr8}X!W#;#PpAU4Z>ej7u&wkE5_uhfA>cy1)BL){f z_`2ZT@@CN#{oc3qds8#tBm2?JuK0XZR!`LMOZuVI7$dXqEMcfub)e6^7B%H7EO z(DU+rfNK!4%%AeS1L3cq^zHi1@<+??EX5x!r~FZ}+dQAp z;EzgvD1`r+aD8}~i&?G*@R#+rll{94uKQDPLHGoIkS7g)p@n>WI}pB`So;O#1c-X^ zfBFkK$$vuSpMrj#_q6kRp49tcLHN$i3S>SscZk+2?#1-`p3;x} zYJ9Jj|8l&3!SL(W2RvWoN_l|`^V&QfumRZ{mXFJztaZHu=ZSZ~{aEEUhFxJi6nMQ- z&j(cgpF#`D-)vO*e_F0b)pr^vn_pi3zP#6UtM>DQOh04QBLgWp3^9VS#I;^t8}bgD zr*E)X3*aaIE~fMi`A5z5O&Rniei@s70WoA;&|8duuODrF`2ypsnw~n3;IZcgd#-#( zc4rUfXVwqsH#I-(3W4wq@XLH){&$i6mPp|NbD4e#`pGWgw*&F1hi{De+QRNCCu7Uk z*3DOo{lphpzuEjWu6TZ%=BYOj9-vQX9OZi@Bf&O4!e11v!PCM1gIO^9zuZ{%S2 zj>BK!Gx~@4gv0h_%M{nE!r| zfoHtG=l89U??o@i_XhFxME6@(7rO9kFB%z3eN1b-`?$dPJv*l3o`!^41E z=Fct86h3Y;Kf-#7zXLeO+JBY%cR>Pp7DIR|z&rk8l@P-l5FO}RMSaY+ZIbUxZ<6ev|EQZJtv26KIk8xqG+{9bm&h zn}$Emy^`$J)Ke87kEYvOe$&rW#()dsH1b)e*0Dh@wM!SA$=kmNy z3ALkr(AhR2Ie2CTQ`bF|SJgjy6UOpP8x=AQNU3cGcqxhR0 zH;Vs>_4>dkR!Xdu`uFlWy}wS{l|Q0>DX-%X;u7Nt9m>7}BTC;`*5!gfR1ReSa_~`_ zPh3&X8|=$36S}(v1UK=c&?&EU&hX)|(FxV`Oed`)T_k>Qv<~}TkljrDdf*j`|9!ZB z$P0fD`wS%XzR9QcYwLk=&G?K%fiE$77Ij^4>2&Sa^H)FPKc{rXI^T7GqYvwRks|adJpm8)Zbw=qtm)T35C+t|F{X=4!&PQpTNSD^DlVmk* zNBVvi@H{0i`@+=9yl3q(A42n2)-F0OU^^KE z@E5*-b_D-C;O(DVUqE(U@`w{#M~8vJ?tl;QFEw*yaB!9O^`@XGjh(G7w( zJeBn!6xTve`1ySGM;iR@=Q&R+>p}p}8-XCwBb&#!G^(_JJ>hvYmQ%;LlfRX;~-iyaWc;)=Oo8 z5q%e(2L41Gf}hMUSss3CEb!?l-p9H7G9U52q^@E$o@3sCkj`4bV z{TTUdMJkU;bmlk!enFh_Ydf%x@kFSVT{HJr;JVJ=b!$@njkPWk+*OEK&$yJ|MEbJ% zywHHQxBY<%SYvJc1uij`FL^$@x;?1M{!gPbsAGXK1c;nQ-}HeNn{sOiyv1Ej0T+ z$?}z*Z}#gOY`*z5fyvkNn{QrH5P!Lfp_4svJfB>`-xBBreXq}Pjv4ziE-&-G6z{<= zN4B5|J%-0Snv10l6_KR_!SIaLEncSp}4vO z@fY~tEkOHA@D~ir@z=KEZ%5mP%Oqb_K2y(6(EWEX#JP~0ZA*L(23|Z&{BYob?-zML zlW1Fwt8mWA2=3#@`E{Qcb_G2jI;U}k__Ymk8cQ62ICTN<$7|=px!U*QL4rr(XTT-# z0^&x6dl!KZ<7W8s3^(9g&HBLQ4L+Gi2R$~By)Hpi$=|p?zn1Y=@pP8`fuGd-z@X5# z#N&w8g}NVWnd z`=v7_UI?q=U;6#}&eHhqEQt#wuEwSjt0i72ovHWFly;^1{rWIqtQ-}36?t5`FR!1M zFg{%&biurb@~75QxSYK2>m&0!@^~!2+J}AhIDUd&qw)QZjwgivs^Ifw=w-q?9ASZD z3$2GJu2j6dh~N8*;-#@3Le`5z9Z}v3T9ftpjE`yiQRX#fJl?{ufgQ#S6n@O79rO>D z^ChYKq~G~Ikx4@OMA;NB9vxjrDacFBPahtMdanZwOMegyue zeM0%D3_KB^#<*YaFJNyLxi9{l%jx{8%c=fvq32-l`MnbK{R!-sr1Nx@n!Ok#`Xz`P zU&VaDdDUnT&+GG8HqTheC#=5`t&7drA?Q`ge~^D6`PsO+y=lz%lth2`shxv4p!G$y zd{1Gh-d_3lhn4^OiVPR#d&SNyf*slo<6ftBC$V#o!Z)iv;(gah4f1%f4{<{67pNY$ zegjvfxY^;F^=4eI==fJl{;ssQ=#g*@_gljI3b)8b`8JG z-%icnb(I9ZdpbpbcRi+h>TRT_%9kq~u(*8vK6`&+@_0~qx(_=HI^}z?jh*OI)z97^ z7%eYcSis}z%L)IK!09Rawy@j4JL3`ca6(VbYcCQ%gy;7;pU3xp}TA=VO-KGJ{*_TV%dOUQz8d?6u_cIIoC(TQnb6 zE(-r5@xbsk&*zoD>gmh*JnYM&?-y5!eI6~JCwcMOzJ0}&VxNmE#Xc9`togiy#m@*t z?x6Sh+VoJu`Uc2TyJYbr*^8ZU*W!VRt~;^1Gq3^Ixfpy|I3_BOVf74Tr*~pID0bTX z54lfvL+%q?@W+8pneW^#XYm2gCu$uu=|_ogU; zLj5Yd9?1ECye~NU6_Phuuk8pgfIHg7_#UWbdC~a*SJHUZ_7%4CvBZBQc=-M80UrA$ zxgAs=NL>ub#D1Kut0{;bj1JN7^#whmXMk_gE9>RDA)i;#br*0eNgv`l+e_^Kr28*g zo;KKl!R(@eh{rluPw&^)$dYyrM$`iO?mbEl&c;v zdG+{9$t%Sv&E@s>=P9or8bw~qB4_wI=Q*Z+V>9Jd>)W2Uyt4iuySx&-8=#MLe>lwh zp)L`ALizJL512qNvA!y>pD`6WkoC3S*9;$IJ=px5mFQvVpWX|&pL9GXd6K&I>wcZ` zD0Mktd;0GDpTa-D{v{*^^>Lo`i`%*m)9Uh{cm6j!e+c}r9b)>nr}YMS5jg9@J`zM9H_YF$JgIYuwvX-mYLALV@{n*&dr9Jq)ujsC zi*l(#_5k|0lG4XEKkU!Dmh+NbUtt;hi^HW@;DnC>csgbo{R7RjSv^Qz;{#jQkv~-9 z1D+r6Kupc!AUQ=ph+c@V8uag!8?;@*&mB&)Fg*2heW6W5hj#U4ieqVGLl4~SkNeja4`X~9p07J2ckADkFINq%bliebq7(OBU~ ze~R`4e)*c5)!E`V#Ss=zU%n5bXv(VG!jKm!hYZb`YQWCip_^0?Q-z6X1FX zC=Sa@a{izjmS6+*^9}UN?RkGXqAUABwSDRehiP0ruKU$Rr$Db_JeLM7wo2)lW%$LY#i+wcxHOBQ_9e+|j z7~}dbMjZ5;uhY-c{XwI9eq8IQg>DmpKbC8+R~qV&e4V)HcUyN&=?U9Mfb@OWa7JGd z1L6Ku+ zB_B9o{vzTmw#T>+IV1UDdlCe6o)y_=%^y0pcV3=0k?;S1x&p7;5Pmi9mG#R>zhE90 z@jvIw{64!T*PFZlBJERz`HU?wpTqoQ{*A%WWF5=+!jXyNBc!|t56|T*G9QuM*QPxl z{Qfo_8PXymsKXkty zT=y&6Ouasz^!n)W%BcHZt=Ih81%P)DqqtwMGy7d_Zhp9>J$Oj*6RX`%d>qm||EBwM zkh){O6+g_ms=}Mi4-#FP#U%}KtIiuX?}z-nyzAvU8}|1a=AGy}l{~W5)2-xcEbx?vk9?(;Z}Zv)C((m13JKL#9(AKh=VYIQc5eJZo_0mGmN zzh`joQ;>OoJukQhr{vjQqVTxfbgIj3m@lu${*n!Td!qC3F5rD9EuS9^DE_KW@(yd1 zZ?bs4I!%u#ljn%8Y5&}>Q+}>#8OW!fuP{5J`P?Nrdp=$3cO;&< zCnt5vYS*TGOXYJEyEf(T%9oZ8p*o*=puh1a>7#b>QmZ`3=_HS>?Fqih{A~0pf1y2| zLH9dIo~XWp<-+AVlexsFQfJY?7aGsuJ6wN(b7^{lFLTu2>3RXz<500v@qS0A_$5+r zQJbHQ)y~Jp#|7TMtl`{rS3HO0vKulq1UUnQf!29-io7<|dGWdF-S1TTP+}Llv>Q58 z-k;p_i>LPLceCdg(++=xUnl$Ml6%X1i+zU@nb#mYM)4EZy^ucXr|}1xC&u*#y0*HZ zMCCAsX1NUa_xJMNN%M;heyi^H55Iny$_x8fQ~xggMkjiClk;aIT$7&*cu6k656gsK zWc>j0JIiv=g~|tZ-WJ(oo=)rwnzxn5 z&C6eu$*-byw54AtKP320-MsOH<>{=RG|nYLXU0!`JSuvw0y{mUoz%j=Sfy~SSAQdsYx7fE`qgO1 z^O`HbZQ$EzrP(?4vzF-nH)ie+$bGjR?&G~+NZ|AN>}wUyhx9(zj}jh~o-gcu3%!1x zF#TitSm&oR^Y{_PAG}@$c;or$D2VeFU{Bx6#-q{2(4$!A+;f-mlkum{S3mch`4f^q ziLT1fvyd0QRXa9yj?it?_^AhcM*7b9?p?}R&jED>D8sdSbda#4(A)w+;JxW%LT(XTLXksmi_X6NdeFO`i;^U!3+! zl7Ri2=w&jW?d{Y!*QZV$5c*nuYIKgmSyla%iMuf#@qZsgJHm6U*J{5T@HC=$+C2Z4 z_V?%W)22RVdRy#qbWz5BjyGPGdcNnnZm-v_*|2+N5%Zh=@7M9-EE*LAjs%eo%u zU(74l_6rRT*82xbyI@G`l4PAmh4px2ekDo!r^W{j=e_Qz^e8CZb`UyRd+&Ex{>AiJ zLH!iUFGzgG^5W-Tjn5@cxA@HY{?1I(AzCEgb;>`eb6L%K$2H0)>t%f+R^73m=;13U z{0;SH`rd@wFVGA77J;KB?xeqD7oguakiMK``(<{kArGzbwAK&R;3c{*2Alx9Z5+C} z>y@Av*=}f@6D`oZWTNwdZ&E&>9TCV*5}%QN$YDQet{;^3ux=oq@O{yXWani5&($Kkq*T0!KkLW`A$5HsJp+3RRi(FG+d8fU)1Cpmi z^>|-Kf4oifg6WTjyzdFp4|B&l$ZM zd@~66DzZ)lVeZaE{|3IP>2u+agCO5Gjw63uW%L~*e_Wp755N9@lKBJsrKI^o$ljc>}JBj$f*~Wj6x5dNufI1monICG z7~*^E4~|;DIRHmyEdJM6*JoH>%k*^!e5``8IbQk9^6eV~$wwg1>Ea<|nW}kIZ+e z-zEJ}{$BF14t#)|g+E`lB=(UWYdrtml6d!g*(QgoZNkIS@odisoMAD3z8d;0P|F4L~D_a|u|7t=!t>l--DULSM* z1?GnG_LMibJcQW0iP}fS>a|o=1I0hthb>)POIcNegj;R2e-UY5%DbiX1{N9 zvWf8#%BI*K$J}2)-_y9G(O(!_TyOq_#3|$850)ifnD!mx6ZHqn?c;@M-}HRObtKa^ z+WV9156-|G5bM8eyim?_JPeHUdCbIyY)2$-Ub9o9)sbi&FszB>FJkw128+Ao-W}`}zJPujYc83SUos9V6fqJyAk?;6>r|c?=sbxeZ*$it$q1Kj*E= zr&gz9`;IXFAV2UlxE{-%oBuuG`Y2OR9iq+vxg-1}4>i&Lzv02Ymszhg!Smka->Lo| zC7-*g^D3S{6puz%j^nTo$zrAVcLLD|PV7R|e7~#?59eg%pA)e!(L%r{a!dIs!k5G~ zg+TmaTem>;tFJGyJfG&Tro+~;epI|{uHCWvHE(x*pm-c5pSMk#S01n1(~^M*Mp3C7I^~fwWKOiOF8IpBFK^s(d_aU*#jU$Ble3Hvfsl zNa@FQ9J4?>qF?!b$GQXV=5KYKZ}bXSD9Z1>Vl(r5y9DEt z-wVZ`o6)WKvO0*cy+1btkC~4(|2qS|@(_*RO|n0Clz!LybDG}^gIMhTIOq4wzZ1WQ z1}y8T;75qR*nB-5&6_ZPjB{RR#_mD~pVvtsUqlD1w)VF;?o8dqMC}hY?!FG%{) zxn4Mf&!}9CO~2RT+DX&zlDCY!epkJ0`wzsbe`|U@X|C5f?@95x=w(~?+MwUL9fTv( zZh-9voO88rmhXer0U!GK1a(1_KLwGrUDkgwzf|@97QY)Oy-yTHetCQ0UO# z+581pK#?9m?wIftoehKyqV+>6|F-{X3%l9CAK7?A=lgt|^mr;xf<4YJSxJ|05e zC>1lpM zLmok&zfkC16uUk1KBJe?y9gCV_-J9bXMQ0=?}zRE$(8T9*R-HF*6#y;^14_q8|ZEG z(yi!i{2=tM@y&ipKg%1v(CMpw*-XsWi##fQNAb&M{w+=4SzY%2?$NMq(JSD|pLH@P) ze>x9KQpfvdZejELYA+k;Wp>o)rg3uMc>We%l+}x*&V3_!%scU~I4|bsU*HePIc(Ox z#OK68Pgi(82ll(4>~~U(Wqi4VW!^r_aeh(q*W|xB$9Zk^cQ*cw^R{@uh;7Tf-$1l& zPU`M);;x>Lc0T#Lrw{Lf|Vh{_y=}^4(GKvbTH}{wIG|@|O6n;ib^zyJhTTu*_kO z67f<`zJkB_hvKE0!b>r#a{L?R)-ry|U{$hbagpMGj1T9hF`l{<-tGexPX}hjtsbb0zS^Y5ucjSC-4X3j9FixABwx zt46R7#wB!_J_if&K936zWFE8HY3-CgTBlaYwCe+(5k7Rk7q?{$}E*Xx-G+SWjAV zeQtFC8xMi_JGGyM`H>-#4;&0Fd{^iexSp6I=SCFQsm-nM* ze%>59*7)>%8E^S09&hBTX?nT~i4I%dtae!E4WVZQz7q5zosYt5gnuP4I59x5{B(_Z`7nT>JT<{2BayzVI;XYZp!t`3skdT!l+T&g8t;n*8M# z>;1*jE`Pep-@g){Q2)eFKHo9Ue%TgpknwoGFM*yW{vGSO6PCWJ^*GfT(Rdus zgGcla*Gt!0Uo=tcfarVh&-{8ifXi@Z4jK>p!;nDm ztRJV%lYCe8f#$gy@>a>%=EocI*j5jbjxRuLjXPA&&4uk9BKgxi4fv)frDrXFlZi9% zhR9o{{)qNh5k3Sz)}LcivwMEM9_c;5{xBOqnSW7)oq@d5c6r>NjH@<|^!SKtW73ne zU$5gDYy6bccquafxWIM?@sIdtRj?Y_4YP};2S6W{JB>HgUxd5R?3`18rS5#|L*zbfIGL)Uz5 z=L)*;BC+q~wPN3+lGyj?VzJxN#bU=pj&B23u8MyhUZnRgQa|8&weM%jxQq{Q;t}bk z;rv1wpPd_(xBbR!y(-tu&N)uu%&VN}zDILD4v_==6Xh?=UYYS1#@Qd4eQKtE@@p~o z;R?Bg&!=Bp)cl3WEf|#f+LfX5iC-e<1^k_Tk>J7gn{yXGc z+c;Xwcn`wW{I>)7iIeczpQ^uK&JFYTNh{B4$xPStaroti%ozufd<+-3UkPTH3f zzn5pfC-J|;3(>YyMg;Du+bP`JHG>;*!|&R{=ejotK5~41eY9?h`rTbmuv~uDLlG*TH`G?04Av`Cp4%b_|PLc6W(fc7IIdt@~qXzZ-Gc z#q^#02lf63)$jg}%H^I!PwJoK2;0V~9?I=L%D$!x2_KLBJ8ulRhV(>!ENj&0s{JhR{HR7SjcD8XGK`e5LI0NxQ!}F0%tdr(_!|KLq`3>+J<1-NYlnnU>nBK0Y*|;A@h_mOz&W=4cgB2+(c?$E&6zRdHIwRyI3ID&q-0ky-pH?X{)$>-Suo(kh<7zrS{-&bZh5I)e&W^^7U9}O#|uAqoT>fo z&ixr()$f|yYkZCG@OV0Jl)qhX)xYZBx`#OK@be6aP5|Fr)U-1nxzY*xof5Z8nBJ5Z zv%2Ipe7_PZ-4M4zZh)uNDL!)LmvBFQVO8n&O2GqIgz1Kf66iJ1Z?({?;|QhK5z?+k z--O!%Gw_qa=r$i48XkmRHvGWF8Gr{L^W$48 z-|td-bs4>6eAX|HZ_ChYHPfqBcfFqRXKI81)?QSmS=@zRd5RD{@5r1gtNL z<9gov+$%&s_<5Ic_TT2hcOrSR{=I!3@9*4_{!ZOK#FOJ^d3&kz6j?uSvezF+nf1rR z?0=EomHC@2d=tGsxmK?$d47Qh;OKn8j{=d$Sg!%5#yLGC@7a9Cru)INK0CTz=c6ui(nY z$Lj*-KhcLgG4K0<`Z@9k%lASA2B{tD{J@8jpF+F)q+N7`jyI9@mfVi!y=*_YT3pF+ zV7)HG(eqyujGvJ-ekP0#Ur&9%+@SpdR zYs}Yje#Nci{$u1*KzICR^7}ux_%pYIv{(LtS|q>1+5QCuw zw7M4EZ{l1JhwT4u`(I<6ig(H9Ey)WVyq@h3B%j0duEoVGv>lBT^q%*!=SS~(11;!n ze)&KyW;l)Re*Yxphcf6v^f7(_OhX)R1wHg$v<`Lrlm}WN*A4mEiq4~{KEv~c;M8+TOe@6JIf}aP`Z%0Y+Rt9Oese?B&pWydf>DxqnDqWs8K6xId4nD!> zrq65YdcJvx%NgIcAIB*BtIaz{^^4}$=0WGk`;$AbIqx>l*CMACo!2Y}vFfMso!9hn zAN=w*`;eiIo9s;ljzpgdbb%Cr11#`IJes$KqFH>P!iLA~%IV>|A|aCHY^f=d*RDTg?}J z&|uF-%$`B+U>6(gRg$(>n_Fkvi~Ka}!Livb!nbq1@E2f)0`OtOxiZrrALIuTJr?2l z4!DNinE#{;3JYBkbrpO*ZD?Px!@8)<=36NZe!cdevvq|zt*4ZF*M>Tl81shgr^B$%rO((t!9`nFxQz4rmPbz{-oSb< z_NTl*BK1RXAR1iGPVH@okg)OR{}DBF|aVpZ0G6{Wt@ zVZLz>^qZ6vUUxXRtF~xY_3ifIb`@|Nk5l(Wt`xL?S^ohBEpY$CMQbPLgCyJzlWv$` zWIANpZ`AgojaTEJk#s-cYU`Kgc_KIBs=PmaR3AV4xVe@WHoB(jjQG4`)^j93Df-yD zZ{ZWK#~aQ8vVJE`PiowUnfw*Mj1GCGC+TT|7KNf%LIWmoY zx$c+0ldOBTI)0aLZ%10%9~bcXM}D7V&G-0ow>Q_1&r5ycR>+GEk)AX^J`d6YKd!|- z4O{&|^`H9jy~y;+`;+U(_nxWW_4SFs_mu!U4}Hw=H{B;0L=gQB`lt1&#cRVlzMRm@ zB~I$rV*Cp+i`8)$Jz=i6&ZR-miyY-~e_?PFf0gslz2|HH7LTfZK?|9uo1}R%m)2W| zk70TlZ@m99>3z+A6)LR9kq@ia=h4vxi$_G?7qlJLfwKNzV%IxZ{)6-n>DdzN^%e5m zpQi;0(WN$d4`KWFbxbiJPD3$d?)=oLr%P~r!^CchB&HQ5ci zK>dB_W6lSzK+X}R>kTB&rmvK)qt@?vJFWh^*?aS=%kaZV-^o5ss6zvs;;-%~ zaDx8OKiPHMSZAj7aHT`EzDn1jfq%KZU(a1(KM`s75!}Zwy>7(yvtDfQL;4B~2k4(j zJzOCnxcqeq*MJ-EF<*LoB~JE3@bgCn+7}tWV!!G^sq?e(6Cp_7{@f@b;7}Xb)^`Il3rbvf-*;4XW)IT-ik}}A`&Y{cnx8OH^`VKxf90=0lc6@q`*Q>b`7aLZ&Z(ZS z8gTpvuTSy>6}|TObAxPelY+FfeZT#=bVa$3UFf)->J4Xkz9f`*KhCdaJ0HU}7khs! zx3@fbqR-8qyBx1GfxeLUYWF2CUDf;5jQ=o#{1f-T*y>>jF2Z|A^5r(>tUq3N*7q8t zJ4BG_uIpG_c$i$vx(U3Q%?q&K+qbXL9qV-XeI%f1UZ_v}+1k2v^?xkCJ}myXtL3Q= zQ+#MYmfy%t7k^v)mcEq+ulHL96mIjc+t%;&{hPs?H~(Go7zUTi?ZxZJv-ZLT(mqB! za|y<0`?oc;%bCAEu5npSj1k;)_`E+;|%}iOuuma-1lkY-yEH%Mn9=v?8_}AI@$VF z@Q3ktP}tUih}4q*%Dl3hn7nA;lQurnv-k@=Yv(EU{oJ1G_s6-OyVdtGdcp(!VZ7_S zt#b+F2z=hM4utCV>ipFPe=!k%(dMbOU%Z504#`o;i-XwsF!V3wvuV9(+#z_#%x4ju zWS$oJ)G@{H+sk}NmHhMFAjglZJgOYWe=>X30yv( zN%Pd85BN*+IyxVO`d*S7!n@$p&!g!))HvrSV$9z#KD~btT75LHJB#;b{Xp~;J3P+$ ziN^1X+%dsGZ`;49A9Q6nlPSXY@A35An(c2P*vR@mCVs+u&Pn{{^AY_8s=M^(cn##G zz0TX=LppDxdCDa@doKC*BuJ!Rz(;m$Mdo=W^L$O_c{THVy*v-%yurI8jU!lMaP`|f z3FSjvu%ck_h#Z_^bi@0ikIj$RQT>XHK2GQ9;^RdhBj2-H)`#ZJ{)=CchrdVqV>n-t zaqB$L{Y8UA{Kk>Y^CvUU`y{@O)@hz}W4e7%C~<#55-wA^>HbRvo##Ov1p5tIH&86; zdh*f=9%o!u_>eCm{qPaP3*cwGpwI(<zNOIJx8Q)-^%Mo!FlV{p7Q=x@#O|*r^4BFsle5J zxBl(caX%*g+Im%Y6T>qs^rL=gT)}|yzuy-}=vBTazfi`db?2%Fkv@xQ{xbfQ#@D0N zdB;C9{N)Yl zitix2#*Y}@H6P}pDT43j`|rdLD}FQ|7XQTFpWJ*{{CEAX&xdK9stb7ECg7`|_=3y^ z%TaCLWcFv{KcfNhuh~~zZ=mZ${h1i~BH{z(Y`d^ewa}nht6m9&$9P}A=!Vz_BSc% zy5iE|vadwx@bvx@#bvVpRB*7~KUnvl7@~X`eo`+}k$R3%;{mC!1AfI%Lp@Co;Uzi^ ztUbv6vA*}`-ffNhgg1@9%lCnf(k`8c%-D;Tex26Ecz{1>F*Z-CRz@yoyocZkRD$CusD#r)AU+&}_@2;c!|Fa9GA?xmZ`x8v*& z_3L~qJ|p=ST0g~pn)mOdJ~juC#J`sO%Q*W*9uBY16&|G@DrvH4STTmeS zM(=ej_x&Hx{SN|<{{gD+jm{Fk89nemw3Ke%4Rz^`gmr^gaw>5Tm2ZV$DVGrWvnjs^!`v6uZtiqQ@q_d4|X6@ypxH0g_zfcGK29{^N|_j@RhGJl2Mr;$Lf%sZ*y8?0E$)9n?&FJT|1vYr-5}5Ar|lr}?DdMlcXNyH4X3}?s^5$q z1XA(CtM~~&jdw zD}H`ry69{8`7_!7Xglxk*SGcn=}q#o^FN!#S1Wii;SG7fPx8ShsYQfCg-kKb{_s<{r$nU=P>t{dhe*7wT z&~Y!{>9#-o=k8ZN-tHSsp27atenTgn@YWMfhKX$P+wbHP&NzV@4J|)u#m+z`NrRNU78H6t8d%zZy1V(dwN6hd~TEPr(UV2N9uuIT)HuQ&!Jl!HFL(2 zulGH?{=e4D-S6D%=YQ}=|NhfE=bW-?#St6PEb|Fbc+L9C-dl6{Lq}%s)n9*i;M9@& z>+HXO{==(Du3P^8>+e6MJZsC}Uk+YRacavSo+m9I;=yyTTYhu9=lK4m?VkVn%fGt1 z-ScOD-TB*5p8u}h^WV?-O55)Pe|xlh{}+2rMP9Gf(faqr zU*y`-|DmUM!8~E>AD&Nd{nWWf?)t-jw|kE7U(@dS{oi}j)9s#Pe6Mf!{H{H(d1Jfh zpYOPx?RKj+H*9#5_yH{%+`f9s@)nQyHl82b?)fLL{^Rf4J;(Pc&T093@6q4+QOk#T z0R47UyXTjG=%J(AJ-_nuSAL}3^ZizzyRhB!=c`&jb?)^Se7Y_F;QJqF_x>^0zwUGGo`XNyj{nu)y7Z6j-aq8PuD1GP`?a6A zpxyhs+?_wH-Sb^9yyZ2cJYUxC`Jw|pvU|Jd`2K6#J>T`Tu&sT-_)@-Hwy&LD_U^~B z_v){A+2gM(>aVl^9(cuNmF&Ix>zA&WHlzMJ`)@o)T-N%x?dSjahSpD=!}o{UKL7g> ze{1(VT;A6fKU3y>`n-1UZ?){3zsxv~)I6zw=b4x6yj%Ts_TSrm{Hl9<`Zpf)mGIb$ z?wB)h?qlhn-Q9nfHPCllXOs5}_3uCPA4~SDzaGQCQ{R8rmmm0cyLYPX z9^%=Y(nsgK`*Z)b^7yCM-15o;1~=xGMav(&`@~tf-7jDJ^s-AYXafI0eV31Xqx{AC z>oNR0_5RjZz3B6&Jv#q^OTO@@^LtO<>9uo@dt$|5cg}t8)+3*M=!!p_*{r*`{^pDK zIQNSB>rMIhbI<+bbH+!tzyJ7reIL2y@BdZLd$0Y(iMyP(!?*Xn`@H*?|8c9I-L>X| zH_V#4)%*AR^^4A}z1Z|$`$tRK+JO5nx;Cl*UiRNv=EL(f**49t1(<*Q=P2-PZ1Qda z_o{aP+0rem?cZbky9Dj3qqS9Jz5g?7hQfAj2af8!xZU%e%TKn2@8+}r(l!RV_T;<2 z)vo`OZ>ZeY?)kgBzd6wE`75s4Fx>9>3`EZm6pus$) zL;atv>R)^E+A9~=UuXZ_gg;}nsam_`PhIxQ;NG9cUq5KC-0DyL@(kBK?T)YhdFf%l zdh~w|JD~W1BVY5DlPUAFaTNiwruM|{PCyn>8o#_ z{r6vvy!L_Yy;j$sb^AQ-9@z2GL%wy|J5Rp*cTXKS^~kGMeCn`|wQ5iIzcl63-@Wcf zhYo$|%F0LIcK$m*`{zA(zu^~;&YAO*eIA(q$Laf>^2^U&_tAFmFL?I8BicP*`{jpz z-|qR-?_Khhzn^&1?Uy{e=ac6izSEhrAKC8<=RSMgs-eqPyy@v}9(t(NyUMT|>KV8G z-}+v4#o3Zaq!i=I6lJAFoBZR@NvEwGs_(P$-`H~LuAlGN3P!qr{pa@lMZ4!Kdd_Z} zL#cmvT9e-gZqE(Zx5@td@vpt_ne4s#>!ky4d7}P0`|k%|v;B)6zW9pAZy4Np`rob% zwtwGY&z!f{U+=i)snbgP+zxhooPa_rZ>LDnA zlTSH$Xr)*oVWh(-(K>er$2hoq33+( zl3zY~$LSwkHuUDxC3Q?1V>PatQ8@(xaY5DYJa_PcJ@)Tre-HfV8B?3xw|ldff9{pJ z>sB24=C1qZRd4;;#ZSNN(@C}K#B1Mo`b}5Q{@%{7IN*_wx3zDtf2zOj^FvSm$^rJR zW`B2@zP@dY*6ba+C-nKpb5Gy0RVy0utBdyfZrl9-9belbchj;}FMaXL4mow}Qy+cJ z*SEUkta}dl>`CW*df@{4HYmoC`mr3YV?`^Lf7eEO1lnVmK*`=x2qu8h8a!u~h@2LS^wwZgD+CBfrRqy+kcF$k+ z`(@W&HMGx5zq!Xxi~ZmK(fbbFW8n0ceBiL3z5d}jZ@&1gZ+iBx&)xRmd(J=e7uWQk zcJxDw{}!Hj(i=Z?z`6bRKRxqJ7tXzN_G3-(TYpRCsQy;!-y2g`4HxOxfAgDcgWvr2 znoHlX;|(`I`-v;|zHr5FKX+ZY)BMWJ-+bf6|NWD-(|@q+&dV>D{)boH)^7YQo&|Ql zGVl1d{)@G1_^Umf^!o+V8=lqOxbRI!zoqW7;h%-!&$P9V+rB#3zTu_18>hZ?i^JocDKnr5H)wtru6+fUBF@g>I}G<4ci+wE~lZ#-kg1E2fZF0w@WpnEs+tgKX%|R`&zA2w%HmUVkC}#<)g~3;*Z!WyKHDIH!$3` zzqtPC&wlsu3vTY)`H>qw`T8&Ck2-hHZ@>4g?=3wt>$^Yh|MBj>-hIUz0~bE^ua}(t z?g#IE&r1)z`P^jXU%vgP9gp2=`#-#T_s`w6{*e1O{*U%n;J>Vr3;-Uj>> zE_bqSANzRR4EYO>)$329(2B0#B-g>PK0jHnzo*Ynm228oeu&ziCfD>mmggl6-*<&A z052&S@TYHwdiy-_H#R1ueFMU!j_YISIlE%6q^& zy$5R{{RUOO?yzf6ot{T_?Vi(hOwWNofCq=Z7~ju9zkdPOq)+GJ|IaDh(m&njb(xKP z6=)q9zJqy{hUX)Ipodw%^=Fsap&W0{nw$KP=a-6W8>9Rj+Ly-VwRRQ(1pOm|KPNIDDAs zV)dcn5abU1Mf+nUxF4SBMDIX;Z>H)>VG3cWAHbbesMf)D9AF2U7z@B+!t+vPij>-4+t(Cejo zeQpMh&9e(HSUrN({9}`ks^W9J^+@8+ol<+H`s57?*PZz8An2y?FZ9+j@(aTyMe(nb zfaBf}em2QTZx{UNg?N6t(5-O0((w$vUZvM>mg^KBHRCT`2S&WZ{Bol9^HuFW}O=Bxvf((d(QiP)$F;KhdkT=*XX{ww2vulO3l8z z=V#XsX1@_+gG-6Hsu%x~^B5(*B)pTHd;5Nh;t`Te_pz%4>jzJtvBrH^f1O`V>>}rl zzz+C+`q3}eUFdVNtBB+F7Pu&eV*0*O;h3**99MgP1HHd_^4}zWXyG@&zh}J#bw+sG z>g$R(v;XQZhZo`bAo$(l#&LnGw`2C3{r5P(4-Tj1*(n)GJ$xk5rWpvkdisAS4x!G~@Bfz_^ z#d^t>e%|K!pA^>$Jto?ZDTCgiV^nD|-iH)Fv5uec*v^j{>v+2$e-xMY37>|K>H6SB z$Y)SIY}ZgElvgtU1_6zoD>_5+3~=a)enFA;btgGkNOr5RQu#7)%Q$a3Gc|gh4(~tS{C81a z@^P@U!XIQ8F^+pn?8k)n3ZI4dhO!@;E1$0W>7Op`!3W$v?~*}!&sAys;EU1-e@F6l zG*0HH*uE$GAT{t`pPWw>KBnhjEdo7uBmU8A*dgSp@GC86_??sdAfxP6;qMc?G;=}x`nd5&VDcK!yvnF?bTUEqz~hPD=-r1QCy+p{J0;d*s~pRP2Zt774|JWTArJKPiCl1JV=~D zd^QK8;&YT+#)EUg9~2KFch2~0)o%-XOrK#m1I{NS@KkuS{@MDBeoFgEHUFt#bYcIs z6VQuawIX%D%kDS$w`)@En)qMESm@`xe1Wc!_rIcIPricQ<-9vYTWw~ky4 zy<7&riQiVn_*T`)tDj#{z9vM0-kq>n8$kb2D=g#r?!MwOnJ>=o#qwzT2*;xLU9MCie;|)K&mo34EQvqRa{sYiiJp1L z4$%`o-hVKElf7TqNBjZ5|Cr5(XZ9bHa}NV&`;Qe46Mvv^nD_&Q73vSjIU5~$;ye1u zK8+6hfx{nwob&!|rGpe6KM&6KkN7yxe4L#JtfcHygm&WJX6BJ_+s-Q%`$a9m@A)t2 z_p<)@MER#0KL8%e2S<>9KEJdCS}@&g9>wB<$;wNU-2y&3-`D~llM|o7IZ^sebbJ!( zcu$w{j;hZFU}F{oI)K+UP8oCFTl8wURP|1z@LXX0FM22ZM2`IjnIg59^-*+C-w~*KS>2rb2{(1k? z6<@@5Y5;bD?4+ytEB(|@pZGC|cbAFXm3IDmprrXJod3HA*Kd?uFW~%u#A&5|orlf( zorCIkY^vYs^RO8@z=t~$@Qr5M|Qq^XD5VpN=!{gg>UccohDa(VjoN z9c#fSqtegl_SyDy8?~Qp@IL7q>(9;|H9oNOqn}j1xGKXJ3GhO4g?NqSZC}XUAg*;^ zTiXXW+%I!~K-#;Ph@4vcaDV6tc|W~RS)6IN1Tu^n)$cYhHkustWX6A~ zjL+HlGjb4`o*FOxbQkHT)_h6qMs~r^OV9)p4RUDs z4RoGNP3ph2Qka=j7V!Z+}j5LGxG=hlW2YbNyuLO!;p3VOR_r z7iyRJd*`{;sXX)E#R}^Sm+9vi!AH%XDN8;$E>xvGJW`Bi@3(9CH){!A-@d;r{Z^$Npjyj$*Q(`T%kR|u+`kw+{REHA z6Sy+NgZ?X$N9d?<9$`3KOXDbBtMnO$2$0@%U1fp)>s^IC>ALe#tuH-?CXNk`LB%U8ok0@7%y>YwbG08-!*!b^?vC*r5E{`z8{bOmh|daEBPU$ z-k85TZdE$mNO%F>1fM~t<%fn-bPe1csr*oXr@)iAVWuxC2nFxar?ua0()3lna1p~F zJ_fqdx`@6`p|307LhltHHu`i$GS2Q$`um2`xBJ&h-^-bvU-R_sHT9e zavQkeG<`b+9+O{J+Ewsg{-E}ok>8y1XV_18M;>sY(6_u$@_@O0=)F>xr;pD6VWki5 zJ9kqg^4oE@()XnVcL_7VD!*mEM%y9g-@;O%Z&(%jmiasx$mdxqmuCqbE#HR<50>A; z8G8Q=X;)aI^xaG7n-e<00PuAMaV2t&&;UFx{FQkVQVD<$UM9mA_TmP?Rn_=0am!dP z;ylv}nY$er-m2rgD)18veo#MiCEgnr`&L@0;~Opsya3Ft9}x@Tvh+G z0)OB#(980nBXXbeg~Y#J&u!%U-~!h?&p}UCfr_yCO!q|l`3UUoV6Fbz^4#I4viDKi z0UU&%RD0~)Jchdv+`({X=CKG2_D8551n`Hi)$daL3j2<`oac2h{2Ry(D4tKigW-8X z@4D~EdZ|LNY)s#|Zj9ggqVkpaeJ;EPa19b1axN~wF?#U&tp@)uQUCAgv|kSElkzK| z50Vs}TqyoSUqR(M<3G63{DyLc@X)BIL~p@wsHXIq+5c*}%J@rMmF1`AH=Nm2?~LLv zlx{S6>Qwr*_ZLd<_VUF3Lg^Nhrx&Qd(AUCWa3!@PX@8^UC$yr^INOo9L-@zrk??r6 zBOh)~&*)~-Tdn+q(j!KXvH1rj_!F#0$iX=P`spY3{$%?HMfLxr9tHBYEG=)vS1>;M zEMJ-N4_sc~Z{-)ja(Vl%1%b?0Q(K(}dPndzafw>|Jgzv3GUrOkjhc zeT6Rg2icKP=Upm+;Bx?R|6+cBT<2fN-w4RQJ&Ny;9cUGA(oDiCSp7c5kDvdrJfE(s z>BirGCpa}9SQUF4B(QXVKY(55aSY3S7w33{k;eO^(# zY?O6%=!N^u!M<|;Re6r+n(cQk(dQL?KCI7UeO}e)!}=X3@VJt-ljq^T;d-!^zkz+c zgZMjK0yuFWepzQi{FcZ%Lp*1H#pks~B(H^$@Hy_N3!%Di@=`i^eOeKCnu@Cm)-|u@ zeiyR5VO-=-04m`J^&5PhTpT7OSFWp5@+Mt4j~V$Bc&X~Y`Sa2`MIIXJ8TrN1+cuPXwUJ5Xs4R@jcMl@^YZ>vJCx;n@NAvsUUwe-t@o2=r>0C-=3lWJ@)=2 z)BjofZc+HPkq-`;AU@#p&s*_9v`p|5{zsY*EPn9$@k>n~s9$aQ@gCfVUBr*ik9*$v z@y}=ca6O+d+y{1%@*xTGqs-q0u7eD9eIYnGQ2VH>>>KPOpU)TmM#qcy$WGY#f8*Bw ze|vu8c(V-kMRJC(4T=5r^*7^s{$FEUp)O{&Gh_$_`%*Ut15pb@VB8i8uU=& z^)bg?c%Js7E8dT0x|5s6^OmjW>sYV*dCRe_Td&Ej>9=hC7{pq?n}%;h_Yeh;9-!jDSu1+ zn`gUI(Dr`6AGITjCujK9ymf>=_*yH#~YfT1eZ`mfz3j(L8Ebey5lhOVkiR!H}uL~>*{U0mp{4Vm5SE8Q?-zWbl7baXk zq5gJsh1vJ9tp|)S7q@}q4afd=bd$aRe|kM2>4oO&28WC4_wo8dlDlOT2l)B6|Mlwv zLyd=d-W}Or7qWhI?o5g&=#us7c&!iAx-IBKTK^j117a_dV43Le;tZ85zn-c8RW79b zLjYdHkJk-4UGEY-WBdud1APYx)AfzT>2fV}!vk4=8jD=xdPk0zeg^X(@wLrM%e*Uw zP5Iqf$lbC$BofDlUw~d6#53%?$Z>$#kr>}6d#iTG)y64#@`0S%C$8V7-o*|r@%K4? zvvGBx#Sp^VnzFCiw-@)~IVROI4*Zso_mwY?euB|`xeEky51Nb^v zR0qsNdB*2I#~70nL+C3GQT5 z=10aMFWxEhAsttnp3!`GCsZ=irTKhF2mA@bBYx~3bRY&B;_J!Hhj)BNznjg2cLuS* z@5XmN#QbX)kUl4WAzfc{pR7Nj+oYd;K5(4#&mH#){wZ(Gd`ytQPL+=oe2f|wRsbJl zVf^F57WtA)-foQf5;9-^r}8CXX5Tiyf97P_Bhz=E0sTqeWc`==yv8fw^@)ChG7$Sj z^R@C`!1Y;(W_pnM!f%XuCbc)=L1h2x^z&$O?_DbA5xx(-)DZXTeQ$5PoNE4kqT)u< z1AFlJ)A=cHFa96PPx*E1$fF>K&l<8$FO`oLeFb|b^Y`Pd7dwYCdNI*?zJWZ)^{!jv zW7-#u`1?Azup$RMPcY^>zwEpL`2%(9-8#=v`$#)0Vf>x%)$0W9U?;;{iT`W!<=zjm z^$jYQjsAh0|Bh;-^uBnb?H7vgPK>?>etFy~2e!}2m;Cs7KBwzZf6e&l{zV2Kcck%= z)sxQTdy@4e*4aorh3yi|pJ2YZ5%^@j31%REg|i6;=%pap{CIuO@=6vD5}(j~P^A5- zKi~N^)o-EP2i-T%pSQds_dj<1N~JaIzl9iAGySS?xx`7HyDaTo3G##X_(>e>YL~+;0@$o$KVfw*Z9fiK`O#;;mvqH zSob_pKIDCW>+V-{+@@zfAaRJ#Q<|Qv78CwnqILEgb9^0sFrzm|2l`;krsE>tW(>wbK0vet!>;negCBT;ECm;5-!egQ0eQy?mu4 za1DoiAMMWMcCH}rg;{tDhe@5O!g<8=gcEf^V}jG*aoG23KR`+AxsZc)RfXTV!v*d` z^McpH&I%{z0lUB(LxK;*XUOYdfY+6ZUtO2vWc^rNswh7B9GI?^24``xw0A|tlfcs_ zd9~U;fq7{c7Z%F86*nmO&$cUA9+USeBlE!DMSd;Wjr9V@H}GfE{2yTcr+%Km^qk3I zTo5{cnB|c4hs8-dGaUVSr8o4C@Db~suB!}B;A`f)qV8um>ON~TneRXop=ZwKJtUtP zm|OrJu?Jiq;rCB+f#5;p%M0AlHLvXZxo?Q#7-y z)6EYXC7(I{!_YP0pNaBIyY3YIZSneqeP=`ZJ6eB7`d0GmWOzyc{8Zb0RPh*kx``bp zd?K#%`)Z$y-{|EcV+ylWT=PUu9U&VQ1rsJ%i zE8epElr0fE*5JQXG)`AK<9XA(BYKtk>#YAd;_FVhUWEO(o^i-OhLV5mo^N_d^N(Sq zdZc;&v3qOfFU>!8Z*T8UZvJuVoAkTc{9_2ZiXI%_{GIZyfNKw)r{n$0reaMD>w&MD9AIBJp6_OU24AZ@CmBDh0=Z|ok^S(( zrJ64=zY_T(m8(4IA;=Z`>ovL3=jtam>H*KsY#&?7iwh>Qj>_xhe_pUBHZEl&j$hk}H>~Ga0rx z2%ri6Ie+DEw{75qmCmElK7*vWgTpe9_xGq3yJby}`#e_wJX73% zq3X}r^r!9@Z2sDK$Ni!5*TlsAq+Qy_{c7KQ++T#B(k$**ID;TugbV;Ujmj=0Vc&Jt8#rw`^Zz7sx*CB1biU zCR>*pV!WW0kLOKZ63r_P2a>NCHh;oNyjRuxnf$w}=zYtRI&H7{=UTl@PWrVxfz`__ z4aE+|{G8%8i)&AlxHc#2h(;vOg6BMLx6xm796jZ8hHubkE|J0VaoAadud%O@0mhg$rNQQ~Tj3t?j${OwHq7;aKtziu7_`EbgM`Fwt&aYSr< zlb8LoeI5XHVS<10j}rA)2vEZRvqnGYb*7i*!#YZd=tu19An&gf`V~~}P&q;GKcn=+ zIn?YwJ!a=F<#tTTPm<&J8t_t)IHV!|fILy20%Fbaw$*P>RNiQ+?*C)?DW~{rfH!FY z?}&$2>f5cZ%;upW9?xt2w&n?@zQpJ?wmiYq1?fD&)Sd19NzN0Tt>5)|0?glD3G#-Z zviRw`e|+HBp9xSXz8fAl%4<(5FYM(NeiHmek_*-+={y0j5Bt!I|Hpyw>1}y5#I@zG zR>=K^yw(!EUz_Kv&vzk_h<*fqxxYf;H$8ECM&Ar;f97vVzAJJJUoGDiT3#|?{~3>c zy{zczg7#y1VblK@X48D+L&^uE7FQ&}LgquG6DIaV4;p?8(hrt75a0Fqd@uT^{hXj)N&ofq2|iyS z`3={N`K%2@U*)TCB!wpo3V@TG7=O{csql9?-);7#;COzv=lf&huW9Ix_$z!5{xOK} z7~k6ZBcqe%xj>(^z5f5mdlUG$st_Q`lygWE;q%Z3f)A+QsI(XU4o@$3Ke%p?vEPk% z$5H-vfgh9e3uN6O><>8DXEnpXh}eAu@FV0rfm-Z;Lwm53h#}K+!dETg{EUYTULp7q zH`e%ytbZ!LYFm`98EB|Lw{5OKWF~e83I|=~5?tq`w~Qe@71IFA=<+ z@I0_7WZz{TXeubrh}QM_yUaM>M)=7@k7GYGk}#1uz|U06 z?@2xP@j5;4vEo);FX!_sY7Y#`N$s}wy)Co9i`pl>*WY%vc7}|@x2rNgqIqbR(f$i(^F6AD|2UIpKgJ&20@@%(Be@P3it z5B$&cOiU>_FF7OoGvboxpK+lP2n_M>@gBqBfB&}I)$=4BS$`;y8_f60ksHtVpICkd zHZ#7+9~3*65&R6iSou7h_oVX>z*j=|XvOe#Jb?2z8JJ-Hr$FKfHG^zex@pb3gw?p{D{)=YPYi8^E7Xx3_e#8C+ z`K_d%;8*sme-U*QFIcbmBClu2x^w8<**^XJEZQM`+~4caqUUn{y8)cYk+bK|4EmGO z&5nb#FTqi`p2_8XoZ|uSQtg2C)BiKqpXt0E{x0QU%mYZjCbrl)DnbukXEuYMReE?5 zzf1fE`k^r|AHD*5Vgb^?a8?(|SK@o2=XJ8~g7cj{_O~WUTn_$`+{4~${OxFwgHP;t z4Jya)8hD1)1HJ3tfnN{mkAWB3_qL4wRsWdmy|t5^S2)ndt#y7|U?;6yl(duVcbkF# zt@>T^UdbMBRljTCPn17C>weeYnC|442K=r;!xVopsIDD3`$FRn;+FxP5I~-pWlx&gIJpeg+++`0M9*+Q7G@KYw2F8Phj|z)9kF^sa9P!Ay}}$B*~v4nk~5 z-rJ)3hWI?7Zw57Kzn^$MF8XE=_Pskao|5zLn9RRT#OnWPCG&}k`{jc`x&yRGy zE$+~I8}YBqU&8ZyyFTKTh_lkU*F3-Cc@Uk8TuX5sT6e1#JxYAPDK6(0j{c5+4%(!AhB$;n{HNn9h}U^r`CJi=uM8e*`J}w~ z%3$cmeEl)_^Y;C%t3L+Y^H6;Kq3btMXA8ww;LoyN;q@DEo7h*%s}JZGdNLn=JL)!r z-hIYfGSENpP5M3FVvxs=_=@gJF|@uV@e}~a<1msR=}CFwCn$#=#81l8T3_QQ1>Y(B zekovFDktq60eVp$GGZD-JmvH7e&nrvPKB@cHt(E@p=(v&^z*!H@UQmJN~{T8way|0)7;}k$p)8ap3>g_q$}bls#d(L|8w3SDRf_X!j_Obk`UpZv8+JQd&$|3N-9g=6u zpW{_-bN(#rtHeWSU-Y8%Yxm14AKU%1XpQ_^3Ch-XB<&CdT z{^-IGNzeE9dXug%;Z%Ns`!!eWLMDW#hqT<{W3A$&kUuzH>qLX~o$>xX+l3*Ql&}j` z<=KTH=ap#}mMMQlqSl|2{Pzd9j9s8`h}bpBUrPMku-`l6d4V^vLFjJ3FJ!+L@rmOl zHb0sB&C4&(e8TTb={br+?7E84`A#3_C=U5)aK1C-7nZ(TcfK<$rTvEIJMdeDF8#co z9P)>TVxGtP&4Ij<9?-n8#4j}eD+0Nqa!=>6zl6VY9sH9=?Y>~yw~l!T%`8mfF+IQA zIFgTkv&6^b`xj`3RE+pldc_hM-^X$F`2m$|UK;a-Jr5Om3>s(F?+7Liz;}fx?`u*d z!+z7_iEtj6#EXqtEb(ZBphz#}&QlKRL&tO!&jX82(|9t+*M`FKG_#)F6H04 zi4KZO_Hc=b_u$_Z|68W+a{=`k*Jxt4hp~xOHNV#IeOcb4)xV@olGB%bTU1bF!WjE8l_D zyq{sPS%L?L^OO_+9g54PaesMUemu+(@)*c&OB^s1$16C0%<^lr%>DD9_S|4k^9QGB ze9E`C>Q9W<@=$(q;@6O$M#69CTxy$NZ2b}4Cxzk5Izx-*L5iKC`Afu*U$D$CXZtgB zy2Z0EKQb>#mS=y4V*RF(`86~zfos6=HhWH}=GP7Vy7m{Iw=7dTG)3s_e@75;aOST{ z(HEuSU>J9K=Unjl2EINp`u#zx2Xgws@*!}>d}#Nfh2zT>@7Avg&;K6<{YWo>UwQu@ z*pu*rad4d3AE(uib{|!F-)+GvptZB$qtWQcp7)tRj|+?$20gr<=o5zPKO`>EZ+Oa= z7vQNtALu%P?lbiD2)x^^e6BdwB{|<%{q^yV_@SZwl8Gn4SB;=6rdII7G27Mk*(le~ z$$L2dZTUmzD??va{^X&-D_(q)p#P``LZfWH(w|`021JbHj!}u$)?nu)%=bm->ET9y|f4Z*crB)elB&2e`EPx{G6?P zPSEhf?fAv7V|_{fWd?i?{vWoKpF>Dt=wLhhoX?L(M%!f6?#nLraeh%4E?|iBAT3<9 zABW}}1RwFM@v7w9-fn#*>Lo_4GdC6I}1JO(z<6dGg$~h3Eu>A6TF4 zQoK2SJWI|q$ZEOfRT`R|5*>2%lyxAeQDhubKDp4{3%y5C2fSDA`AI%KyZXTxpV{%qNV)nb;DtgTudI^@bm}==J;V!*@_U zuX&QgK}*p)TQ?3j{8;Tb9EUSbS>Jc}`AYpqx?j(FN9U7i!{-E$UTqY;YQOK!^Ghmc zJ)?96CD`92aVEUl@0k?rCv8+Yruh$ip74IszVcp%&-C$K*7vuEpXAev^O1%xvhROp zzK>0wSNVD{+rN5n#FIWg0{dWw!wxZDTYo0^-6Qd5@SWey@@L@7F@N>t&pZSU0>AZ> z-XOm=s5jhx(i{I}@m8kZ_<_%d9FHB|ZQtLzdSe9gznHF{P;ZQQO66Yl2Hi7V?L4|p z^u`F}3$A9p;aa`Hej=6Eb2+#k{?CY)q}LL;D|@xv}?T!r{t_@kfV>m~J))el}Mp04vhz`=PndEXPKcsiue@nqfz z_$3Yy%=@u;);Q3J$3zc-f4tBvL1 zyr4jDSok$B#m=+(nm27O+#?Y#B>RRR&znYK|7kPdeY7|N%9JkG|C%Cp;G=8)aic5b=rjz)uA^)+z zziXMJ2lq={!hWO=k0<#OiBjLS4CUKF{u-cn$*#yeU<&?h4-H^ z{I-anRM2msf7Y+|uxvZ^b1Q%4*{P8ahV=c@_WjR?pxUnw|$k`CvU z@qff{T_0=*dEkL|pf;=JJWv>GS>NmdU5K6o%vh#q3Rg4gD@q^IZxe_-pwmdYLib{P zqgsU@?fH5>zIC50`uQ&hv0dasGOQIqX^Tk6~el^4i|vmAJBQ@q{~qGLKtM|=^N<}V)T z2tFG%U-_eye;v+)$=LHB%RC3pxGB!Bg&)f2L0i5|@a3HsV)+R+ zf$5q7D-u6jJ!0{K^A`BIajS(7v0;q)G4d|2o39GWvps*+=N~VcmUb=Q#Pob|%olTV zfR35Z!Spy`iR9p0N{=x9JpY`PCYcAu?D>H9{6EPb^5==@`0R-GU})Hy)7l5m1553S*9kt?71~hkj(@(4d=FO7ml=(5Hxu1W@8>j(hQ<;7CiDBzgY0|J1Is&KCK{#n z6UW5#e3|Icl7AmPTJrCsEzT-}!!!zCN*o6-u-zG*R(`la;#R)iOiMi5ceQ3 zHt)NQe32h%9L>9g*AD*^a%UL-$P4oJ0+c%c_W4%N zP0I5ZW&Tb5Q2pF@dE`6K^->P|Z@)Xj>!G8w79S8~#)th*OhUgClSqg^t$dJ%{GomP zw&Qug85SQAyAM0UeC_yrF7p`U{!qrL`zt(`>4kCeI;}r0?DGiCFRZ=i>W} zninO0B=RUNpUC%!RZjEHfslETwPPk?^JY13GV!9q`(qmqbEG{_;vX8np*(2um(-84 zc*k=dub-Em@$(nu9C8CV;jb0c>kih#Zm3!ZHaA;hf5m7UzcDsWqWMvqi9gb~#1|U( z1ykbZ;-4q$5Q&!zuM>>P3O*7C!A?;Aq33waCyEznVZKlF!2fN;bBfQ9f1gJC!cVeZ zVaB`+J@upOme8A{*gvQ92(FR-^&B=i=MD31%I_bo{s_{zqHOQXoOpC!)HkuO zt9}~Fe2;YxZXXj&x*xNfl}~rP*hxFDLHrjufS%Y*?cZ)<4dMFe>$9kf)7lpo?j9SnSz2-gik%uwHkJ|^~8#6Zqe-?U!$D#15UpEFa zDtOfQBjND@K$4^5v>uI{;wT1=dL7D7;eJYwm-G$I`?u713*gP^Yt~CTpBVFBcAPr? z&^VY7G|qFRp513U=0eqrY52X+qjcX$e35p#6rZX`1W&BoSzcVz&G~@1K+!-zvI}- zVbI|mZ*owN_{=!<D!ErGwWA-sHA z{M{eKUx8im+y+pjV4R4L%RX7)f$UUxJYl?Gd|bZ}KR#ai)vp@UWBEQOr<8Y2?2+du z;XbDGLCh;B)2-wwj@hX4XZ5H7-AB^%IL$=yi?`@~M)XuXF7aunIl4C-XQs&QNHkBGT#ntZ7BxvRcCB=hSJ_dLiO4?nAyA3k}UKi*|a_4*GcYMO>tQ~r0zdr5B>;L-IBJZ!w z1JjI%-#+5%``&Gx`^K_czI13~kK2@-`RKj#hPr!SvHXpu%g)F9;U>oCzyIz>uU?ja zU&h~o_e<5cbaOtMKJbqlrv7Dq{W~j;YWn=||NiDhGtX$*_glBDeQLnPH?D#Y7KYF1 zW{su|pSWeslgl@A7ZiD(Mp@RDPpDjX$!n9ZJ8bBNOa3+W!_%+cbj;p= zxhwIV4R>6;^Xu1Ny9TlMf?qnnWY6Eszx>W^pI!Cy_2cjPRrM=ReNgxPf21Gy)0NLW zIP>?duYArlHGln7aUDAFS8GfDyCwdnI$8Uy2!Yc1&u9MW^0%Cyy_){j;rqKCKYnYm zsUEoQm;XHI=s*AA-$x%ByYaXqzR^zIPn@>6wFB-;^MVDfr;zi~zG!iJ@!ZyQd+RsS zi@wp`ipg}jzh(LT7qO31@*Dc*)N#MdzYqUiWPDzJok8Dy^X2^eGX4(qf8C|AZ{K;z z!{6KIk(bUJzkbQV(@%SE#Y?q^zfxWOkD=r1=FnLoVGyGEeN1cr1?J49e~bU|-a)0G zVz#}Z{+RF8wmcHM=GOmC9e&1%OV6&X*>}&|H+*ILxtLoK< z?Co7l?4!Fpwg2tC)lvOeOf|AvJn^2tRvvQvyMNrY^2%GzKWy~ckDi=z(;K%RK5fZU zm%jDdx>Ik@>37noW$Ad*FWFvTYRP|gjxs{2OK%NPJAK*EzI^0-Sn~f?cvhv;f4FUQ z!{a7C;?jS9_kTWYc;SNdAC`Wh;<9sozCO}DVC{GF-^u@5-=9=>>b5iT%fo*shMl*4 zxGevk=8yUJoAI}=ItZ3h?>U{hrPeXJLZ-Pum9$wPaXQjJwIQy z{@jPp9oqWwL%V+QnAp!|{I2=QXxC4B)jv1Y-E>@DcLjgOC$F4eP?%Tn@s&69sy~mu z^lOOYmj1;TO}ne~Q_#A%hj>;omCl_m{R=Zz=)Na(AHFTwuvW~Ls-Vxir!~)RJ>aOx z6C3tUFIe=Aofov8*1CY*#Psauwgs&%P->@seb%hjQl4wx``k==QAg`3&5PR>wWnL! zTGH)}EiI?Co_eb2*Yx5=a6H?0woxmy%K*QX5Rj@L~xZf%5ROx9bo(metTf+9;~w)?{Lkxj4%M5C@-svA^jr}G{ip5n z`Xt>qlTWjR_oEoIjjkgtF`U1E|8>7T%;ON$qkd3^AFo@RvuYd6Z*b>0wcRv-K07M+ zOFfUvjpoHHMP3Tl@Bp3Tjil?5Qw-T!C)UL8o#WJU{iU^XHO^3uk1vhsdog{l@c?-* z>hXKT6y$TC)sz-Ew7s+Aa=$c_5jbM4(q6nt;E2Zrj@H@IUOW?k;~eC_(71B&43YOe zDL2sri^8|m;XDZpO!iS`qfK;9usIj)(RGQ@=doxP;{g;P_;c&TVLUe|{UtvpO#daa zegxYs@pzo)nl2Kxvm`oNCM1+5E5Q4|!JnD-)Wv|NoVc@#@y`d&=mo4C8Y7imy> z^SUJHEqr46=j^!LZ#KOZ4xipWJmX{>4NC9G9&#UoZz!ROmQLTX@9Xz9jcH;!Svf|mte)h0DI}QSyH?kuKz5a5sZa8S zXJ%C%7Y{Fur#gw=x(q0)#p)po(0}m-+(Vg z-!w^kZmdiAZ#<9l4?cZ)-5{(Xq= z$c8e$o0N}yy{&wd$prOwQpb_Bdi&a--nRO>N%0f=s?tf{^Y#7Jf%km6cPQNx9irDN zls?(4z>&B}+N{Upak(xrS$Q6>=n{GoU80cNq#29B91x_d-be79^L%h^CA>SMEuBL2Hcpb^ zGA^#pD-LWluMfixvVL$}S+`%BPK&;92FW<|xjo;-K8Ky)^=`gLdq3p%T)@Hmc45A; zexBEl-CyayAJO+48o0lu&=<^i_FntNc?5ZI5I&^+rI25)@7JrBdTALCx;H!e{paA~ z{jMlqqvQ9-1$pN7bo_dbje-6We7I~k9LS!^O$k3KtoJPZA^A6rYB$y2DfE-nFNxL) zUs(TY@ovJG%Fn)^RGVhL^!+uAhxL$e_Y^-)Q16)Y1_t>&tlzBOj~=A>m3B>H2f45C z1ilLKiY@`cb3%piF_W~sY8?>%~r>1sX&A9LQ1<&*y_KDPUat$am5{~a+rxzEWB z9Y9NRxkmZl%B6unDD#ox{t3w)>2;q^)2dh1Ukuwb)R(-P`a!~9TF-yq&Ier8Gfs#v z_5HIz+D7z?`55KDInOCyER9JysYv1@ov%6YiJ6Za!{4Jf=fkfcd+ACZ4CUW|Ls1`j zxVSp^XnVSUlX9~4ea~5rdgntv<$caGrFo6KANBbC^_EZL>`yrx?7T|-Exhm1`^fX< z{SHVJvhebFur5OVInUsq*w@L7XTUA*!QbN8fqN8REgn$6nd$>>Zs(^y+_H}u8-=@B zzUl{m8vHx20P>0GM(3r@YoJR5ji+hh^Ml7WjQHYn*&ALwyLFpK=l$}cCllwrcHyi`e%o`+eG}f8dGg~w8hY2&-`#mh z&sAT(V4K@tJn(`ycK_-Rj{5zM@hN}WW$-As^MdH{b4Lul;MvoM-8Sr^F}FVTKNag2 zUi8G%;HQG0mtXQk=UrE9^XnPEJLm`Z9=I73L?-?_E z+Q(D3U$N?QkEJi(X~H*Gv>kfIJ5N4*)Md{;HuB%gzx|cxfXSNs&e>x{-Pc;4Ul@7e z{rBHpJ80+b(dQqx_}jatmpuF9o*b&Pkej{Gx2NA5xA6?b;hDb?&tN`Iy0X6_YJ_j< z?0iDcyQ|Xt>;a~M^*o;Q`{Tp)p{Ehsz)$>lTQ7?9@fj~R9)jyA)XK_!3;rHNBks2a zU-3FC{1$0P^o?yNjiF&Y_&*pAq4V}b@3VZ3Q$DR1{`K3pLWk8ch|k&-62ziN^ywnTcxth4-}=N|8-{Ia_deU0bJ58?Wjj=~SN9`bmFAF#i9AGE&* z^cT&G@VuV%**MqkPhvlS%Fm_pXhh5139|n}<8XWA;NbU7OyUjWF>fILH|7A=r?&ks zX??_d8>!td1DB1sT7I0Q_sf(&*Q#CgwESVK|3Z9f$5)C^zo_p&WcfVW$m7^EmNsM` zCdQh0-_2gw8AS8-SgZq<5$6-cWgL6QGyFUo&!}8yKu4l%!i>fLS<}IEJy7jJmyW+H zE_62u-M4ajLgJJ03w7VhR~bM1D;)u*xeWi1M#}!fUg)3vNDuQo!k0<3vV5g64-)(y z%kLYo9Njw=yx$FU6+aL20;UVnpq=G-j{Ei0j;HXNSS#S5US)qtVa+GbV95_iqk~$~Y>n(Q%y4a2&7g+qhU7{y4)K5x!{ze_w-U>SAeb-*G%{cY$xm zBTN^>+oZp_y3Z$Zk@hp3`d}B3OgotRAuY1ANc$WYI3c6#+<0 zC;85Vx!d|fs{dn>H*UJzwDQj&-ESy#a$#pc3v-srC*+RXO@q~$j(Uzg?3?hT>=(c} z6Sdl&%*$@Ec#4XDn6B)S=P}(c;~vHIL^xO9lQ_s~U*8R4e=CfqjN+*ze&xF?#+P%j z_DGD!>WzJI--vs%W0UZIBkpNjv_FOJ_tWR4>VuxW)?aVR%FhNx*{}2AwEY2u@_zkc zCr4Z~59HbWAN5VYUf-m%i+zXFt8E{pu?p`p)$WYyNuM zgC{@S^1%&Ni-EwJdrth_KW3lz)~;0}P9ER9`-C4&PmWr!=kwRQnb$YH)04xn)fZT6 zV*OA4C+SD)e_}n2{n1M78$^s7dP?>=TK^OKAXy&{fd7Fxn#o|_NC&Q!&{I_ZVqD3u z+`*|eztk)13ujO?jeBw(Y5odXf`^Qe_4T&M${XlcALImmVb|U z8_kdW=e9i*1%6>x@wl%*6}chI7bz}T)LZ>to?#fh{?hqRhfja^&P!f6Z&}st2X1@& zl2?9m@5QGNnfKy-2Ol`$**EXI^W*EW#bDgoHAf`2JN$bK=3Mapen$>jI^vav{cb#N z%^j=9o%U*HrbSyLdqpNFrB~3A=}y>jC{NUV9`p9=>8kS%%QxF9-C$=}|9R?{MPV<< zPleyp$aYcBRqT=Zx%@@S_#ps5?!T$`GvAC4 z_ZOP?*m?!?9@XzI5Dz|c2cu7dw<~JLBkMyzPGBBo+pBZgKDsZc|J|kb)8awD*X6#b z-_z=HGmNL*tR1xbT&@2XU8eqwtQ#~~xqu%=asnQ2W_;THt-e2_=MRMa8L{ssc9+__W@dsOu?SL##77mJ= zLXLrXSK}Fihb|d^WQ5Se!U3Br{vCbajo-ufWMQw|9^sEDa{SMw{;>`t&&@bG|I&RK z_n05LuSk2SF^JE_^u+&lUxge&f6@E(^nP0O1V$zB*zdK*gYdhr<@&{Y#NQD!Hn0D< zg;U{pJ;HDR5{4US&~e~NL_!aOlUI8J)8Lsz)wHy5mpD!=q5AY@kt9%UqobcsX{)q_xI8KAv z8$doRd>9$((_8pvpyFHS2@#EZ`FimK;bWS%H-it{5A`0D<@I4AKtuNS#fRPG%NZ30TI zT}rE6M&QYVK5-Xe;3Qw4*8Y<}N%C*?q-Xg6k(X$A;M?wpF{PtZkpltR4?22vwC0W z>UkybB!~_Rm2O!7=6F(9gxmN1MKe_M45RiOFOFY0ZPt!qfM|!pa%Mi@dS1(YI|g|o zKFNZwh(DD6QOv2<(|y+Rhr@hXiFJcw_Lay|j4!!-5c~P0FOY45AKJ~@{-;=e#W_Sn z@hlbf@WSgFM)nE&r>@ZA${1Gv-73l0G+9Q%o~H}roet;{F{u! z>x3?JGLM`1g6da=>TfcEdXceskKn<2JFPiJwh{OoBlm!s{SefsWqnWMVR>^{--qlk z+JSxL=ZI(ZtKILlUu*ZtPDeqfLO9B_`_7YWM-emy0>?7fi9fNW?0&*2Zucv|SFrm8 z&$+_aMeV)=J=Q4C8#wMpeB7JAT&`czhuz<@etMoCOcaIOQyR;99w+!3{0=^q^*+m& zp31*(KP28_}cvkA_4u5h&+?Mf z&n5JV`kgNHkwfG9wDuSi1eUwB`sbc6FRb^(9-|*?H?!*B2lWqOoA}-O6{K~=PZ}wD z3irABBJ>-iEqC7w-$h12pCI0qrTmU^=4sR~^uGIjr91gakO#8~?R<-&;&X)T{K>>L zj)vC^=Fk|X$>8eV72=z*j_{4@J8M^*PPJ2x_)E?WXoBeCX?%Wsl;ajC8BcNk$RLcP z5qDVWqkF5@qhFWzz<2A!euHtjUhw;$1$^0U`BJ`H1HXgtMfAXT{PO0N$tuBv#GBwB zXnZNHesEgg2VvOX!#UkzkJAas1JUn)x-+a@F7G>j?(r6nhR^GE{dJ}-`W-(YQ{*cO z|FN0q;eWqR`2Z(!aQxGlA?znWZL=J}|M5s(Uo#%j!S{D;dq3v!5Prp8ct+w@7LSnU zM(WSVcl;2~VLd_fj(RSKy_fg}$DN{QDSlkU5a-}EoCg(w4}$k;eHcLD-&p)VC6Gq| zW)=DBZ+&XN?-!20pg;DH(Jk_)FnG)x8LuSQBsYLRpho~+ulD5A#y?1JL9Pjo^@LYX z^mqiHJ0Q7<&J%emDd$?>v`D-mGTO?UuBYjFQ;{*Da?ppzxeLmzzd~~SuJlWI;`O|! z)=P)#h2vprSKPmgp40g(USfTUi^Mzno6aHn5}&cY#&zq^)8?HLzTofSC-IuFJpx$$ z%11U&Fx+o=UfSRN0>2n1*+;>%i}@w`0Wd?W$TsRUT5Wt1FsE{&}D=cb)qE*D2V5ib)>VwIFMJ~V-lx73cp$gG@PqloEd8XP?p6B}cX?9)8+FObqL+2OR;f!xT zcpc+E&JJhU9>9Of$tURxYu}h&`+!<~xy`_u$KCbSC7_ zM31raG@Ji`z&px~DSpm(^hnibq@e&WTG4r)&4a-H4~pO59C*>2@vlmLL;S0n@4$K? z_ni~xNG(57^B|fv4?@n%&o~X#UN3$W$J6XOpFhtV9Mp#eewNOwBGrL;l_?pQ8RoYq z{2Fx|lz;8I3a>LGZW8z|g8R>4`;nx!lY{|*KaJmZIv;G}_HdrQmdkfE5Jn*X&(AN2 z!f$|mj{Z>aj&U^^Ue9sVPjzHJrH&8JJ$Y`wUlrl?18~P+!n4GK^5hqC>4JAQJN}c5 zbr9T9wAsw|MdLq7s2m>Wr4^EooV-o*YNj*)bo%_n@t^3O_Wi9B|B21g{=)GepFYl! zBoF?21nm84jIUt*xe@q>o&vd8pBJp}d1`mk;$M+J-bi>Neqj8FpHUDG8Xwe?5z0qt z#Cyg_906v9;zh`R(S0hx`k+70PyGBE%NHTOM0;DqF6QR9z8!#c6W+q}Fyc}wcXIlX z;tZoVdVoV-sPj|uvbzFlW~@5kBknm@l{`K0+A z@g4cq}!_w`s@&;@h?4|Hc-n{F496e5`zgdcDSF%JnA& zFPIMker}(~1ps_`D$jq~y!l=n?F)YWadE$d4=(rh0p_*!B*&EZ#`pxBtOu-IYd(_E z_|=xe3*{>a{v{4%`9S>8+<1h4d^s!_NANwEalc*3tJ+fI2*zm=9Em`#N9-3sIN`US z{g~K1;al>5xP2LS9OS8`b$I4{;&1f7em}l5W-Q7nPi$-Oxo;Ps=SQPo^3U`8b#YI8 zmM&A?Hjn?&F!Q_5C)Tg><7T-$B8`iwzW)D<|1Nqdr(esP-;*3l+_n;wgWn#Y5X+r)I1URUkxti0|3h-ed9hKl{}+_@rZw8kHZby z&*vi(Tdeqs={&}9C2vXMJ!r`AzEwYN7RtNDbN=oJ+f(%C;NNP(zr~9_*K*=t;#=Zh zmMi4tHAuV@`y3=r%kv+Nv-%}*zo`S;Y)$g?HB${`_`!Uh3`!}>h zAkWOd#n%=85h|wY6(j zZYeGRd`47Jb5TlTvY!Pb_ET4--7Ksz>c}W31kefXS;|DzeAyaFgCeoza7=Yj#|Bq^-{KX3CI-pS16}!cWr$` z{_KvJ<=6P?JFDTBFZ~tXIk^J%o_jKUxua2&BZzX?0 z?LD$(JobyM9@B*dE>MBNUFzZoE*=Ae1A5^ne#Wcq1oCNF&VR)G1ZKm}MK6l4{?4PU7_9!k<@-E8gyfjmeLdN62Y;}C2+amK z@e}@VwLBybCGl(hq!RmvE3lT>O#NAS=qh;;;-?cT2oAv09fU{oMeuwi{C(MPjvM@- znowWj-iGY}mGgX@uQbk`Y`^`(ClwQHdw`$#g8C=DLDx%h#UFNDgwKNYx<2-c;{E(_ zCaFb?h({`V9M2g)+z*Xc-0msWTey53k#1EWa0@=a$N!)$tjV)%9!dvz0#^ zqGQg!`gyT_eq}>I{%!teIIe(vzoPlF#*BkqApT|lFp2oxdbSUW?-F^lq;DFj9@%^5 zi(uYt@_mV;Zt1+)5S|L)!Fr=Ff8b|uRYc^@&#yHl@@IqRIr#jNMjV&M>#4o$<2(m_ zkNk1Qub+=h>n$Qra;`*~`#O`DhXe2AU63B3II^^B`scia96n)J$^f}8JfaCXw4=i5^UqWrG-TegqG1>x#0{isf4^zC^$2*Nj_(J@jT>L=# zwRExT2yuW&{)auM+2)(sbN&kHA^YEye#z~We@d)(s^60U6*+DMjPks;5;FznyFxk% zy+J49hl2G7oo`tE1;{@E9`o|sO5xAv&obu~%BMIsl<}E-!}?voFY%}2udfn6*mWh# zXC`@}>UI6@NOEuhnnV*jl8 zyT05B{YuR9llN`LxQJg~7kvDmk82Cf>qBy$^E+gI-1ofR%B|Idea!2XAAJ9#-{no^ zr67K{x$+lNJ!Z^MmX9-3zd-PMT`wwk{m+4@7Fqwx(L?6x1@p?j&UpcRdGqJOd1)h_ z?**)v$RjQ2udjKmr*TXhm$dlDIy|p8mfAO>an1t2z|(khFn_cP5JLnBZtF+!IXe^| zl6jcMSu2mWdJhxhkr<`_e2{}L0ML*3NmS{1gPeax_`t?vp3hgpo**7g&sUTFBm4J7 z>}B`cv3_75e9(tqQ?-rN=W1WW`5|FA`^hd9^h^1^WUs9sLH3#NdE8Xa_S44~?m2!$ zaR>67D9>m<@U^{;NA@*U;w^~>hV6Z&#smF+5TJlPtMu_P*ae*+d`efx;rWhjKT-$! ztVDll^+TPwoI@IOXvT;$6e1iaiVV^71w4ChwtoN0m}w->BmgW2I5)uq{V{Lw+pQW52Ak7)4j|WiJ!=i!!JVTW|RLU{+Qya^6QER>-U(rd`E_=BBq2q1Er`A(npt(#TwXjMk?P*#GqTIBdt24*Hz3qg~{DQ$@S4N=U)H((_QLCCK{?9H;|s})|K1kj zTVGz#FU8w)eA}<W|( z(FBk6vC5I%w^FI=DV2u_JqrAj&ze63Fr&Ld&6nC26VAWN1oNx#ru8p5j^&@D*Bz|q zT0gVII!u*k?F0Bh@T}`TU~Jf1e?J=(58dOJ=6tLe?BL~i-lYDnZ@-~}s9r3dW;&X9 zi{59L{w5x0`(~WxDAp(P;>Vo#QFXAi2fVPI_4l!5WdB{ceu_`JqeuwGvN zT-A(H;}*J9%`)}TdWO%&;}kDCKB6DibE@W|-ncUA%|rhaQD4uy&EXaAZq~1tv%;wz zI!nK&w+6${slD*8#QR_g`Az@M9>1SD=%C-m_y5Vl-`(-nO$Q$_|JnJC$4|KL+E-dn z|K(n%AJ|d#74OaycK^W_wteBo)6cx}&tF{<|K<2wKfLtKuH)0)mlIx#{uGXviatc4 zg){lR7oPvi*d;fA@05lAxbClO-kh{=Z#;#!8`kVFCM<_S8urLiQy-F_{69eM;&zIv>!ciPIG?U{J(X4=M-r_znXdfsBOm$ zer4J8qptXNRqMbLhIJqD<$s@d``f=<^7^VtUplLLQXlx~KJn_eS0^9%{g{9K|^Zi_>kVrrYRI#e-*b*}7$3m*v+xXZyP+{j0rhr+IUC9lYmJ@1JpL@}bt}uKMAcF4zYklEc(~+4JOF%O>FxRbzxko~DL?{>xp7?`*i^;+PiC+;4AuVsdQK ztAlTv`JG#q#w%lgIOOeZkG(3L-~547CFHLHJ{5%JqcTz%iWt#jX4cFUIzZR~NIk~1H@civEU z?<WU+UXQza;Pn_5>9F`QPCw z0)O#0N|X+{Jn^&bzwp+8bIzZC#DwHS?Wf=0TKC=m{@}3Vo_wTd)%>>8p{aNO{#lDo zs=e#}qYjz%`IqiK>EAm(_R#ys9GiN5*Dqh1xuT2W3G@?{?(%x3%4F2xbC&Teg0(w3 z7n$b+p3awaUB&FA@t6$e%QWxU5&j_QRm3yNZ#C9$#e{O z7xW(WIleO`{66A)!%&Z4rFfC87j?V{wMX-R@~d~odn@I6z35+Ae@FwQNhqhGj^uGh zBh$=k`nDVUtC)vI;H%=iowT%v`Kb8m1@kq$pL>t`rTJY}@j~;TM%>>BzjYT}(O*%x z)2O!>L&N(joHXE=jOw2cyW}WEURvUPI6speFXkJP1Bs(id~Y9cue}fN!%gS-h}Ip@w(#4Ij(#^z&vTpL&x@kW zFW|}ic@g2MnfX)s){lc=-bZ|z4#p+=>POFmSxvm(Yd_)jVw*=zbRqn>=y?I~Zt*%@ z=bbQT8*N0~nZe&NF%L*Lg6oI37z`Q>H&Jsx$kwqAH}DEI8M64x}!vn8Gt6d(R*OZ@HUUqADu z)7A|9O6xWUe(9?7Tc_S#xohI(?T&kF+UM3fCiVBzWF-so(gGrPldMkr%ojs#lDb@2l{C9OWk33V`;b zuZUue0{3QAl5`mTl7O=vSJCIVFdm!;=LM(ks9nzA)5P}|NUl7;)^gs* znd*d|CHre!$0PYW3`-@_eg)%!#!2{fV$$wywOM_i&)rGg9_9N~r{`&TBlp)C)plX< zsa@lYm+yefeyG$cr`Eg9Os5AtuBk|jLcgh-(i_Z^RO+U?soz7P>Z!aln&I!>6JCfu zCTcR=F8;uM>XwZ3@5D3oUh38a-={h|Is~rxX#!UwBjZiP39i)5@eTAm)tSjiJ(thj zN_ECX|6zes;CD|`_)p{hR=H`we=GnI`*l*K;%&|-`Chrefe$y-P`~)lUZ?{ z#E#?pJ1aIy|9<%eT;5sHByd*nc~Pm`nwWkd_wM}s_88x%I-8oLJ$Oenp3Yc@oFf&i_f_dPuII2sqe?HmU8eF^M922!I-NL6L^x#1uqF5ujM28es8L?qC?)RNDw@!m2u{u z93REor2QbjL2OS~yzuvaQ>&88ftL&z1n0dopH$+#lkiui+~2#mz*Dsj@EnKol$6H@ zpdKv08Nm0`pYJyJ;?AkwKyaI5UCWQ#%lnM)*=9DLAwFwngTLD73eRRf8eLV9CHmkm z=2K&iJ3`)zHF0~3YL;{TMP|9wui41-T$$_^ez5pXUN7Swuw3vnAn7rFl3k&Col>t- z>s4|+;OEhz7I}=Sp$Asvim9ce#CtpvTAn*kB73||h zQXYJt$i&~LF)`Ms;5*HGGo)7&cpfu#e68^62p>n*0UvYlM-B5i@*;tk6LDWHaxiPZ ze7vSsV*bGTsg|!0h@k>UtX9T>g<|GMry*2tnAAi51M7o`wOhNZyzVY~jP2I(=wIb< z;x5vEq=Dt+1hH4f9DSgai~OWk7U;DD6fV_EG1g0|m2O(#vUb`Xr*NeeE^CjbDO{Y7 zkrI0Zup<23=hQ09%2=Lx-UgtMz*R@{Xy`w}>zMi;(HClG$4t)1z35RhwsoW2tDYWv z(FVD<_9CMAYv3#T-A&*`zbnaU;Qr_u&7!{|bQvV|+5d>5o&)&k>T3O_qV!qmc7*V= zgS1nvbgLHncBT|xDZ`*SsUGI16X7E6NSb8hCMmDk$mR36KJZ$-LGZDGuMmqD1s|ym!Y`?6j3027tpAeK zuhIH7Qr}d6to-${@IC7>z@c(?yoGn8!mDtdApIEg_6x%QGLEw0EIqDLaQ;o<{97KJ zEk2wlrUc(}-d29^9UkC)131#8t}%>{)aq1En&fCc(_x-jv|%HCOWxYqRA)_(;JGFR zJkP}w(NhaQR{0UUq*kVSgibwtMGt>dyyw3UJ`#8f>sP-m^+R+mPN!CX{3q!6k1u!p zHKFm>Y^3q0I#U~!pK379S!kd1Uz=I5u}AQs^lwqVIH_lYlq(;%Z6^Pf=7Zw=T^tW> zJ|5;X9_E=jA$aF1eoib8Kfps!u1dqXIs6K_SGj6~JVSpJ^wX+x1^3H%H`vJmtz}rUAgEPRd zz@wRI`40HXy@$ztjXclrbLwXU^^@wXRz9yLKA(ve*v~+@%4s#pc@A&Wwf!((EVTWG z^z$rB-vIpxFMs24G|M;vcMroac%pHM-dH4Xhxmi^o#4%>?h*b7@aaPTxzLTqnLi$) zn;mEV_@VcT;vM$Sha+DvP&38#)FK&A2yXJ1d(1q%wh(m2Y$mme?F{jO_;;cY7n<1t zJ~3ty^E2iHFR7lDc@Ox5_=51oS2XjH=*X@jKbCR&#tn-ibeDoXU@&zq~&; z@IJ`}z29O^2)qwJssta+5;+aoQL7)|-;{|bQ(bJ_pflt^_-ljkSE>i|v7EddrhKp# z{u9R~bpBkY@U8{laXIU^oE!;XwubZ<=}VDAq5DZ8{Yd&sp2yMtrMNo`c)X`?er3IM zE$S0~52Z`B$;kYl=rYU92mCp|B*PUbo?!c_3-zf~c^Lbz+5Rs02P{m%vA}*6-AL#!Rla$Yjh$ihG3mAv_4Y?Irq|XZs1z z8G7CJgXctl+E{cY;zCH4Pb691p&JJ)|3Ne#}9;jL!hx?0l2t zAvmw_;|F+;>7xFL*Qts4JT(8{u2U-xmqDO*pPz>`$<--?+4|&}ZdL=IXFL*l>fNQ?SGZ)r1 z1>vgE{_MQUc}M!Isa1O{ao3fO(#Ma#Z&MsDCnO&nR|mqXHGT~~BK%^13*ZK@j90p* z2(PJ5#P7u)=JP6utQq37ZN5}1a7#QMa-P+Cx}ME`GwQv)Au#T-yDAk#_bb&|mPESM!q@p!t%q<$nL^=&*|*X>tE6z{g&ov8RstG?m& z0LY8dAw_gZqFs%1^A$|US68o&UV$(!hrUM<{Q~4#+)RsXxyp56`eT=T+ul;c-K*u z4iXRQKwPAA;EEp-9O#N}xPSUyFu^fY;idCt>3*2PJ6z#SDZI5RcLMRI6MY6T8m=fAFCosahkV_jb5pn~-gl7TLk z_VhiwzUHqJre%D_j>GD$h_(l8uw9R;oI&sKy~Ftxsp}(r&P?hCSFYC>eeOoOMw)E^ zKiLnm9K;VZaXsBx3+BPU=KXL7a?tp4_sF9ZI#9lm!7gg0dqndeIggXxA_ z&asm$M9CBAQvc6PxI+3@z3%HntN)zAJT3>{8PSNxct(Dl@?~7}%p#9a zefG=p^dpXo7??j!n8VkkUd3|JqZOL>bDQev$}aJP%cWCfImvIHyzVXSCl$V=!dE$6 z`Gvo0m|9iYCHyjNb`ZXMrpP_#Tfray#Bl6W9F9l`j!1*Ran-M-Uz>LZea+*t{$p%9 zwFmd9N$Jrf^r%`V^hmA~d?u?rt`}R*^*XD#9^4Mi%d677tC-f0NqzJDG19-rzfIMd zimx--{;sN8$8b889nx;9OZrPya(||Bsf;(Fo+?ZeNzWIj{ik2p?N@RzJrdZ|9cjQbFNf2Fj~ zaZD66w#YcF|57jx_EX>!|q#&78ueMHM2k#ei& zaiW{x>kTQl`G6OmkxzGH?mb%Wtvna-)g8iDRb2vS74vP8anF@{6+B+pG0hi^->q=m zEpWs$9^qa23g*hvB`bJxYMCx2_zv`5oKJ6KzCK;<6CJD|R>>aa@n|#X|UBJF@kc{C4EC(|TX(Mkno_L;NA<6Gh)uzZNSg&N^__ z1bd&L_w)TN)uYybn5=fo>V-(yZ)JZA^4B16MPtK|MlkNxxIE49Yy zFl7JNx^mTdB)!))(#)m?$UlW2wyp53zzh6l1da&FO)DzszJ%4vIjhwzueSpq-Uj}YPmH_dWvYEmLEDW!i(=znN^Pky^*>g45eZ|POQhehcC zepR~qepv~*jfD1Bs9(_@fTIP7kT|AU7SNML)fdo>o!t<@O##g{(~J(5WYN!#A>>Ww!g9>E8`FJ130o=4tu&x;K05P z_Jb>G00*8|sUE5lyJ+JOeeK6F&Q8)Ev@5sA^4SKsRn8(i=(u)}aWUO#ekJ2}oF=?U z!{j&s*5|dI8udH&56s(Ow?vP3hW($r)sMG!v>*;NUf_(X97I_T5cg4hrPEADE4w{{4{$soB5RfY>gTJSn(K&s*BxA&FW=^{ zmz5uN-HhVrM5h&9>%=YyoFz zZkNtGY`#D70r%G-`pXeN+{7x=(qC7X=(WTxMf!u@56*`nPpS_sK7D(kdZ|L~LjgW@ z9j(C5s=W!%CvmC+@pW!qx7Q;y;bYqD(%^M<9WXq ze%F0h^WIW^SN@t)C49JG@H*i`yDnQ?9$_y-c1Gd{X8wEHuIA~q?WFBlKQp3!osBc= zdS7T>1o>wEN_G1F7V`llAgcTp8n>hIuOL1!KT&;c;~A0Fil5bjACW`!$8wd|UnxJZ zN$i3TSDnBUQ+Q;b6~t#dDR)%gIil~ZKiwJA@|gMo148?nR32y52wmDIDqeNJmAT-$ ze0mm{56eE=P#moM^(gnh7*9%P@Z*1p9NGPnk@bql_4#;&n);)b|53^{erD?bAn*zw zV3|+XyL7#xa6WJM0pa5nn95V$DU1x7^?GpZ$evSF=DuGM=5Hs$n zAe^@>k$Ye++$1(kS2P&UvJ(-u|{i~=P%e@?jvg<~v z)v+d;XJP+bN2r~t(oS64(Rfoqywlhg{{z3KUhDCCp5~h&|HBo&ZLLoWzLo9^qEbG4vX^h~%B3snHH73(?QJ-& z;*4XwN<0GgD6aAuXFVDHJ5au#S>&HEd}py{=EF{_d>I2>mPL9zUOayU2%~--q>kV1Fdg(LeDR#97|Ym(x=GQ!0Le zd8x`pmB_`R^#MDDxJ6U{@@Lq8DG|3Y_4D(`g+$Kx7fD{sTT#Px*FA_xXn5 zC>pP51)(%vv3(FPhaMEb3+kmt0k{{UPZs8w? z);n@>5i?-0>XE^sZ)_aAn)ds*p=FsTk!10|2+L_|h58YW+Qs=Qzp()t$LQ{s{v!`^ z|NeOe0~!R50TLhVY{*DGt2YC10`8E$TSI8+sRTMcz+II3i<)R zcxc=K-(Lj?M>8mT^+9zmGy|uYEQ;ft@HcWLiEi18rSn7W=ZDe{+woaB z{7w8YzdyvuLhFpWUe$h;@-5r>MR;HIw$1Z8>_fFjHjleF+(q!!6vCIr9b$?v>)*M0 zPDu!lbrX9;&scoL)=7K*`sWP6e^TvplI?T(eq7C7iod-Ce`?29m@eX%)Ee07MqJmr z8pm;OM1L*7kHi7mCaV2beltkiV!eOWFu}WBx9x9yVI4x|J-W}I={`G;?$77by%au5 z@tK+X?GC}a!XL_mFmu)@zgWMoz^>W(hVpBrt}_(iLHQvd{}@JmjgD`P(1qnbLl0Ol zv`tj_EZ!@sfcIuR*Yi?t`>~AQ!k1hweB+--a-HzafaR*^k{;>bg7~Y={|dub`Jlr0 zpunecWX}bt8&@m%Q~%|#HHGv$I~|tyOs0 zPlK9#g5k)`=bzyAbe-8)f70(~gZ88A`LP=9$L8}QPsO`$2UK4sRsNIK4(ui43%(CAdrHP<&uKSL$@nB+C6wO^@=wq_AJrFOKgN9f zzMxzd_zOkX{opUK9m|{ljud!dg~Z>t^a#xhbY5lL(7amnZTvVz*gvnCsQfii_)GK> zK0Efae7QDlJE|O7KY7?i^4!KJqH3=^p}UE&y+s^c;`y4lXXD^z?s3|l=FiRfQb5iM z*T0=+!9#HV0DLLkegCSc9|rpBxmv(4+NW8vB-(WQB8b1JSV?Z(Y`(Sk9z(T zsvrHPz-{v>FZzkx+jw-ML-6XyS5KF5RCLHV>^WFrK9qb*#6dkBuli-~MT*ahg7`dn zJ@;$~HEh@Lm*z(4Z6?4yBtwD`a09xlfMA;D+Y4GZJ0WydRrE4@_yZ9LTB zcrVh`YnfkkzL;8_=#Y3tfn1DMd}%w9pA7k7zR1%zCF17JGI>8Ff1Qrv-=X+6emHGb z{DonHSCt>yFClWL{$wO1XUacquFU&q)vLdw`o`w_x16SW!t!^4UM(vBMaHRk4UPu{ z+rHR1$xWs5%EM7(KkNowfuSBi2I3|U`x6#nXN-8aJP!xZMoFn;j|>7`pbHn2P^f2M8?uahM%l5vFOqad#Cgyg{5 z5q$7wB=1k+`BCaNXCwD#<2biv(|m6nS%1I1g6~tEU0ErYbqiYVH+un1*j}OQ=a&*( zC>Vun8zT_&uT*Df{a}GZ_@lUv^xrw4R`KD`_-PuN5qv0qP@ns8xS!M=UEKmlCG~^z zC#(2A)wyn+mfy?eomE}Jmy&k{3g4srdynuh`&%?W=JW7UH!auwuj;SfQAv2AI27x< z)E(;>52${J_RIIVek(4ca^U4w>Ax~7{p&jBZCQoe;?-5S)7q|6FYg8Yfz-_o^Fy8- zv)=%)blowC-_)&658)wKU*ZSWFHGH%<>x^Exzb<3ddW3fo)-8k)W52%KzTC_ko@OR zo;VfLzMNV3L+nF4(^=|Wi&COD)7Mq{VuMZoJc#z=5dKuYx=`962PV`2rtgE*Gz z!79;%vcH?g!}7}KdhLZfCrRzj#$SA1VCpuNW25cdo>V&8`GakLfVMwC^=$Hf<7kfy}Bdeb-+fIdp&nTSqeaI;mG(MfJeeS4eq&$pwK6m>c0_;LdccqJNu}kJ(0VNM3F%?UhnbgP{=hkwye?dD{-p1>i5;}( z2w|O>=WV6OTS6~~<=Z6gP(HpRkB?h@_}XFjz`wvxR`ApHEx}LXOu zoabOnFz?^YRlLos5xEbZvxR0>X`I%^(JH&dK6ddH{eGYE*_rARd6azzpx?$|J$pWC z&2p(%!|@)xA3D#)=I_6$aRSwQ<^f&jv3YJGxiQHb6b@}arSYVs!eQ-3vQz3+X}v0i zqvFrXw|^GC<#ps1AVBgm=UY78lcl&)X}L|_L+xXJvs~vN+)rw?><2Ms#o5eZVspyT*Mp8S!0-N5h^)KC7Q2tLRTYaH($rGw^qwkjS> zbx03sUbs`4m41T!05pD6`U&WJ(hGW?@*BENC-g4BuPIKq0)6H81Am0=h~(p=|B(O6 z^que1w+;Rc=_NYPnB>&z@pj~&vfN7lrXBtO)4!DdC=RDtr0p)yeirpeKgzdQ>Ftts zQ#~R#>MuB!|BLfC+$FY$rT7i=2jv&pf5iN9EyhQF)}eH<{tW6#Ip=ZF_H_1v5 zaF(ENyAP-JgPU(#E^*=BQ(A^8=(Yg&`SC$k^Zc4R%V5S~iS^Q!Dpd!cz=qTe9* z>x9mJ+~zEj5703r_jVsyLdPrnw_yL2u9f2F*!Mbv`OlaSexUl`1JMUIkFHYAUxQs` z_*#sd!`7wnbyjPC)q-EAQuRlTme)x6{0fO%&9`wYn}1VD^W+STNY)YYk;0Muy#$)y zEI>Ksk1fQN_S3fEO8a*IKla`P%(CMs7tC|ly0x4tS(|s=vi7niRm<9?mTq^cS4pi> zcS~yZdTZZ$DI3`X!<%`rN@G4`V{rrrPI&R^QTns=V>j&HE~-Y#CQlRuTYKb+V+iuOg1(EH2Y zZkBN#l6j7s0gu_yF}unK{r!>5an2{>JU*y=2;->JE-_-~I4_m|L?%3~*r_b6K?fVzGE^MDTb-k@?JJ*_ibcyqB z`^CVY5qNI|bYS+gv>yY?)q`^R^P_w>JdY>@=W(=i#~&JA=YH1HRTS)3BfpPV`$7DX zhSF_9UmvX+9eX}hdQ|vOXcvw@Tv{jm4IM9*?+DA4hDl%an@Q~8-v0fh_ap8l-<*5& zbPAuH@z=LWyVyTBgfF%H&1k=MCrZ}N2ODh1_;`9Bau_X`{_>5lP~YNtTZX>%KX4p< ziyTLv?z1xD!tcz&3)t5;jE=rRI#Ro?=oK0#z)hv;$4#g26=diyKgNF2`b!2pil=a% z&@JT-6wNkx;HFb1-lFQL^7}%UrmcO2`Nu}pCByrY;3Kh{2k&Y?Z43O?O>dvDevUsX z{7mLi??<|s$JhAV_P6srgy-n9r}*9LeO*hWvn5TjeqTI3l%C5C&ae74ihfHzdn^s- z+2R@HR@3XDw2SlBW&O}!9P<&pE28uv`jf?xsMbxtQh#>Ywl#(ReYitz@?7P^;3L|v=P#x0hEJ1UJeNVsynptQ zfpvx3&g>-Sjwo~WUCjea$&UG>9$zu*;mt>TG%6Z*1^oA<+zpTkFC z2Uhq!-InZJD8yS=us@?bil6rLTHw5Xth5;5W3#}GKhwhlx_El=q~gO>uhj8<4EsWb z$J_O3*9^xK|3WbT9}oBUh=7BBp>G-^b7o+#*gZy#G~yax|N=BAIkV^$?RC3 z9>E806|D9ceJ>h48oVEe9;)M5UA6IjgZ-xYHI~aC8Xx{c(xbPZt{OcW|Nl^9shvL2 zb5YBGWNld6xBXW9(W3rti(hdk`;CXmN8(~b@X|@oZ(zAlen)?q^&R`9VO#$S2U_&& zpMHxU!MPa5pPWB@w5Y#ZaOK=j_;9pDye0anonQ9Zdf3&l)setf;4K=U$M^$_$BwLfYa7RcW_QI zpz8qbJ#{W{qdnul0ewC_ut+*g#-qPG#}neY_Zq|VHN-RbVgB6H&#yc2b(5P$uOE(W zzRe!s3g41(;7|Ef{E;=CXa6oqc;7w#XhHKk{Uy8V#A}zGPvx9v9Ue6O*YA5e*2PB7 zQ=hGizG6exMb7@H^V};6@S^lLh4E4U_xI0EY#5-PV0yVf4?p~DUB4eLnSNvTl7;cp zEbsl2JI){ef^f&Hww`(3&3H&I4DPX z68IOReh?Qz_ak$dH=bY46geNwhlV*1=D%{>z90FzeJ7o}>8k02*DbQV?LX&h{C@1$ zPV&3jA&3(@w*BkP0mwADjsrP3(;!@;c8i@9du#yT{bw%#vd$B<`d;L;sJC*zU;?6D&H~g z8wg*pFFe6{@AO;DmS3%Q0=oA8&||J4R;2i6?Y&XwKBs)u|Mu@eO}OEp+z*le+I<(W znN(cAokoXlFdf(9o-Wd%8!>z5%W4n7djybcm@nt9H#@oU!Aop?xV`Tuy`O8`m5%v0 zxf{@n>k0gw{k^&C&EL`N51sU+?dAD7Lms5)$hnapZdb+-8k%B%wnh4T zj(c;>p!_z%S7~rvRaDQ$K65E3w}a)XR<6o&%QfQ@^uI^{Gd5l;r~6(P|033<+@t!X z3R8;%3A1ZyMdbD`mqs=J6!0hIlKi6PV?@6qcNlD_<=szek*;ll)z_bIQtI>vu2vHUCk> zy~2EhBhr2zf1q?q@%O)HDzbYpLA)Y}dG1p~z2J@4 zn_M^k6j}VUAl?(G;zIO;eP!BC>fZQpBc(6AA)=g;`;qUL`RD#yhS^kl*fiV5FJ6ay zJ3pA6PxFl&KKBWy_>DgS`Lqe}{D-2eBgOo_2Y43yKmC!GLwz6`;JOU=S82Z4F+LX= zze?+W7}k4;kHP@w+uQ5uFBrdXpf3F-`-C#a^JT$bo!?zFm{Xto+xHDnZ%OVqU_4si z+j*j&WPR+fl>Ta_f283F_ZOgp*BCu&6jcA-CH85<)=sgZ^Thob?!%qrIE+5ZI0sC@ z0lX_+r0c`3VC%rYM_XikJiNT!zW&g!@w=MxMV&wJE5xO+erYdu9sSmRhW&>9U>#`v zlMTY{{d0PISiWb4<1{}>uvv~LkpsrB({O<V3oblkT+iAAb^jQ0VtRR*inEdcFa?EfPNZ55fLz^2FQw{kyweqbz6dS}#|d{7pZ2 z=&_G+ycH|Q`|kMuVSZ272YlS0zHe0f1HT*9^Q3au67jRd@2>nV*6+4C)_K%UyM&7vmH0N&cqa4Tc-Uf%NCfp}yA} z#CbBiNO0dDa_|(}FZVQ*pVW36Toz~8*2{9C$#R7X%k>21sJDi82=KKA>%nlX?RN!! z_-3bFhF+xnv1szSgFmiXJG3V(m)7_j?;~ith{Jtha^4N4rz~;x3rHD1pJ^GtCGLI^ zsl?ZhEX5|IEOGiPNF_dh4XMQM$Ko$b+-c>neGl zCi|Td0W0gBDDh(;yoAfepXysv{p(lIF7;TWi@0wE^Adl0)!wgD`^RijpYK-9#J}oR z-Or=kul27=eP{l)(8sNve&3Fr|7trkzMc3p!FXl?&($)%IDrH7IEC^O4+8zK{e$}@ z{EF-x?lb-C#E0EWoWXCc-?vVB)_aJPzCY)_X?}ucw>;bT9?c*8Y~NSO!T)pu2kCmo z-IGbz6K?0Ky^Caar}=*Bm!HNs_1sGRlVm=4|Jvf)ob~&aex5qtnaZ>`>Ue}6((@A1 z$9OV6|1Q^OKo?4{Hb2?#?IiTz{QGIYPWp*Ig>j3%@GJdxuex8}!J4pf<2~LKo)Y@( zpeuV174Zubx&&PbycT!F*4HmN<%99B_Oahz>yv)RQ0`e>=VRYlr`~V$*YS={&imOO zA0fVHVeeIXO~EB-4E#;iZ!sC4GyBJhm=l^g5R{$&w@0l@LN0<#l!pYRlY-S zMA8qu>dz)`o=Eu^(i_aa^bv7Q%Zxiit{=EDP;h*sMFuMG#&D(F|yUg2ZowpgZ^Q_qp;5Ox7vr!t}gY<0QB6je6~R$Ha5fUqD~SF@8DM6+Fl}q3|v7 z18yP}e<;DH_+^Rzp4jn(@5nutgGlLbJ_$V*>o|GmZxVDQTwxAdB7NttV(=y(3?Jk0HU*6gJQmp|XS=MuAn`t!=~He6Bg zK8N@FV7xr=NA5eEzJmqiDqbaho_GFk8K53#=Rp$piF$fiZWBAxm;Md3St6?^Be;@|mGe$zTHvUABNiVe2o&wZ0P zD+A;&#i$|UaecDy7Ji{|Q9JFVafEzXUqb&iz-jLQYFf141xq7KQC#!E@9+uh)lHy? zq~oX}X|u%i2!<}d52L)Khm7tIcHwu&zW=y8wifLBl~0^-^MRg}$7j>{M~B9jzdP?P zJBw?07;v`pPvH;bpWNp`k4TN9ndDD{e&_IO8uA{}t3%%N9r=miz1Ef<73c$rd`aa0 zS<0j5ddFPkOeiO3!1o328Mj8bHSUb?&2~9-IFLgv{fu}y7}!OcfgEb#al*?L^xMwY z$30&UE&4ER(a#zNd5;p3C;n1{Exx*xVW#! zOi&+n&;B9L0XYxPFGsXrOm5laNXI!}dmI%YuN=YdDrsSf_E_WVXyPu=6vzlECSOww zLQ^F#d@I^jM3Jq2$wftPwc>=n@37kjvmN$}d&_M!{I#w-`_QANvdI*V|QN zy#*O_l>}wX! z8FX%Nn*AN`dcU~&YU+GVz)olLW%yrC?aM@7P%h7ZXFW|Hp?u7-YbEmcEax@*{6Xg1 zy`V|gMprOAjv0PKjIcrdfGnZQ%`Fu9UczsJ&AmW zJw^CiGq8sWUZwy2ZY+iWv83N|tvBgrQ+BALFF(Y7+qUFX0#{{rFUdg`<* z=IzI_*SF}m?dx0QliF3VLsk9O>|afkBY&GxypO|jv*zB8ulx=Dh2@>MXVI=I^DOf< z#t>x1zw*^{$-E5CC_+HJng1a@Oi!ieyZa=x8cR6VNbLX}3<5n@-?N_WeQoGoEjx>iKhe*=JFo!MT0oYP2@(omzHIYBy`?{Y1eR2Je&V@eY_Z&(*i*?&} zevvo-pl_k4qY3|S*JA}fu7{-k6ul(p3E0oI%MIzD^(=mE2V@CBLhhKV$iQ8Q+h=1Iq~?cE`$#N^Qs7qEeHqDqiC!Q}M;x-mNd! zwfqTJm-6wG{@u(jn~%%?E63yQ=hYR$>HP$~-)FUHaH_vt=l%ERi1VQOUFx0>U{`&ka*-(_zHhN_x&K_hh^(eQag`&PJEY8u0XqERHWSz z^5rhb9+5L@cht0CzwtwV1Kmt?W4>NP5o}6V}I!N#n3Op<@oQKefCA}3tRgM>c?|#W-Yz%qaWFVI}n_Y zo~!*bfBy39=~;GJ(3R2Iew)9;FLwU=*C>}%Po6~k!mn{Yn%Y-@P3wMR&K*qcZ?TT` zd&aq7j@>hNo-@zuWVl)93bIehwtL$C*qxi*b1uOv>EMVvkg^XnlYH94CV+?4Hb1xV zB%gl8`#?wS`!auyBm2DedV+8A4f;cmVO_N6`*q~~Lqq@37T@vyV~PD>o%^(AvL9Nt zdDi<0ZXfy4S)T`qf0)=k^X%5+@tX2;`Y}%A*gcP-1>1-98Qt0WN1mN$lI>>LJ^z{V z3Hv_P^UUt;{ms1H)BJ(qC#vuT?FfDio}isPJHULh?_FoV>JN`*VzUpV#&rT2oZmdV z=Pdda>YGj3J!M|G&nKUoa)$!GM1Rd$cRb)r=X%bU?}wkqm$KGlORkuIrkev_kx|UK zcDtg-_q02l;v*9Yo_OyA^ltU%Q}qR(GGCK!f3Ut1yI5eyo$!1jz371I3MYidf|K^cU$z#@h)`ONss9| zI_3_h_<`7eIIpJXdV8YzMK84I9`&Aq*Ol<8Yfvk(KZ1_h?dx`K=ICDmJEY22f&ZVu zk8u8^aNv8&u9@g-fu1a!k>mFIQ2IQqg%^&mQ@=WzBi9Xn=tq>tb8(j4G?By3pO>c3 zMP*%P*+XSMUYH$phwT&Zgx%*Sq;>e$+={e-^XWyTw6nJCT$eldLmLx2-VUh&JU@bz zc$@B${UFX^yP3Mek3%02dgHq^%aQF{BiXmgxwF*A-{$jZKcYPq=G?nbPxyfM-yEe} z$Nv8{?txalDXZu0gym7vM`HgoJ4M?5ml0>j>^>!fuVi+oi0_Cc@lz8!r-xhO&xl;1 zKXmfG);r$H@zDN|VL!>VOP)Pkg#a$K>&yTS#(Q#`EiR46=R5TM3?KjVyAMw$o*r(^ zQ?Z8m^mbr;E@@o;;GXZ6r2L%N8SrbobXwBDPJK6W=nv@jUPwHf-DiyQ2j+h}@2)M9 zZfaG~&1Tf2eQVz6ezstAS~WkiE-SC^w2h#=#Z`aoarWcwo4Dt#e8}5x`A$jr{z#|& zmT_o;@t~Vh5Whs@e{7Qsu8+NUq{itU``Rl2kDQYb9&bMyuMC4nW1ieF?Z$fFeiUDL z4zN%X{0hI7dQy__GQ0kJP43@Ix$o`Gr4yi&y(nk;f3?B+)3`c!;k%scG?7|7;nJt9 zoli+St@97u(-wO;?eO;_!|0{w4l92L`}J{?N(WGWk8d}apGK?xws#Qkx~@cC7#}D< zW9>e}cGdpBE2%f3^elYZ)@S$UtUTjnJ!$cJU3ov?7(zMYmqp`yMd5pQ;5*O1T=`p8 z|66VS-Iw&|qOV#0*O;$-3)x){2wnh&_`l=yi|}I|E*XE7xC6KQ@6J8Gf#dad(=M}9 zE@_JPeoWd!oDZwd_Z>RpI%mhRM)yH{WQB8UtN%ysz2+p&)6c&!cuEbf*M_Db$8Cf= zMPJfw7Cd6V=R7y5Pq>AW$?wSMQ{xoeYURd|QXWnfjlYyD=x-d~?R=~>!Tt+YzaViA zZb81$$D^;}IQ_VL^d1!O{r9Ywny(vCkMK=a*x!6tlYFya^HPv`y}8x?27SFH zDL{beWn-edVDo~4)n6nm7J>)-w&9+acD!G zaM<~kf0z33qm6dBJL9$%HU25|^kJM(ieK}v@a_01zO#Thd&cLI^S7HxuQ~4s{U+U> zVLZA?(6``e(&I_bji&@4)sq(rO|@?>xF+p>x-UX@^tp_Ay9pW8XGfz&^2^?8241dD z+IO3_l8$U&Wp;PG=dfmQtx*s5{vVY;Vtai};=ixbc#XRb?NLA21^pR(8t+C}zQ*@@ zEAsWdIOpE^jduRr$q%ATdPF&$|JJ*y(Zw0#LN`%x4zF}~n`Cg`(Jk);U4`FVM|&cp zUvM9>??I2mc&@Sh%aKxU?~P7p&^box_cPlsYs2GZ_)h(NcV(3I0Kc{$3BImCzQA)O zQtH1$9&ea$!Xf3}jC@<~?@Z}W8RzRS1>SBjIFW4nc-#2!`<;7Ufq8r&fyeWoDB}A( z;kt5>t;5v3BHoel8&?rJ*o5|oUmv$HDx5+M&~K^Ec_^9Pr0^e69^Z@hy(r(s9$@^ecWk*bgdw zi#>|;y}95(Z*OvU57&8a=>42}#a@B%x0*lbw$v@3a5~-xtGn4ogY}FbjFqLmOWj;^ zl;6!i>PG#3i@W8deK)_ioAvw6?$$;7Zu@fD-PDTNX*J&5lp9|= z&3yB_r=JG=Pw~#gZ^`{2` za6{nd`d#ZclKQ(#G7XK^1Zk)S0x1TF@z1&z7x^2@RgMM$xt$#Jg z3H?R+dByXQt%eWs30J%&t6WFi>RUrN5`FkK)=&9K%CL?96;pf$;wlwaAL32*ocKwL z&wFP@$Lk&}Qtx=snciXkY+Eh=0OY$^iw^jF#%*--x%hZ<);Rrp;u&`0YgxX_;P3MI zo6T&;6~;`T8>2qgUD0w+s=hQO2vPa}P!GrF3Mo6A#g&y2vD{s`bbg7n>+T50iS?%Q zTqfO*rZ;TGgm@7P~bdXCQwH;8|g_gjt~q6L%;fHs`uz@f)v$dx{o<`U&b!=Ar)`|Qr!^k0D{nLSw-J8bN6eug zj<0F(hwtPyD%t!q?m511GPpJo|J4%K#RTBe z{gchZYyzj9uVBAAKzs%IhWV!k`7OD=FRGh7u5;a}-X`Y)l-onmI`_HB{xWNwO7Z_K zWBqPJhTR8=e%{9Q^TfCIGwS;Re2Z=Hi#?zHFBH}kz6ED~G|D%3BcJ^6kn*$p-Rn~N zQcLf+f&9=t5ObUlI2)&}o7;STcaU%A1b1rtkh2|nh5EI-jnBhxZ_lm&I^wH3V07X2 z5{Yx&rf(NaUOx}N?cn=$ua{iP_KJbL&3k{OaHGxBjhLq<@T}|E$2WJ4-0@_;|CKMV zUQf8vF?bCxrmr=lVWoqn8*Zah>>J2O+`gji@5{p`?}`n<ZG6r6&35I( zXuo(85>EI>dpm;W_ZmLhzu)TKIBM;y4N9J`MvT%InN_s zp69sT5XSvwfy4B*%^rV!)<55~Lbdw{SMc56Q*@<&HhQslW>sGsL4UOO@LbZlEq%r} zORJoplKNo=@}ZHyx!>k>2>mLYtE;*n*b09q<-av+r>5<|4tA@-d6Mn-)pR~54DYV% zXARDuCI8X5*4xpaojZ5kX8E_Z5TuR@lf_o-- zTQ9fXg`5ZFV^~~TPg1?l;{1J(A@DDy<3x_;@xL3}+C#^txBL4ft|xf6N6*zkUW^s$rr(&nD06@F5cF#)@4D0;?S{(h91r*t z>_aZme=An6!g@o6lPqWL?_&FI6jLbe?X51dznaynvEKb2-?Rs~js7?G{c4W_JfwT- z*{*yO@m8_#_D;j@a#Q;msu;g(l0NV3=J$E%1%Lzg0jsCy|B3w!{?d!=r(gV9P;T_J zj`s-UgVq~e)!%nPkHc@bzkHJ43o8bnzGHy>(Heh8jE7G<5msaEG*%kgt(;_jMfjF* z3fkFk1L;b;QLV{xsdqZYkYo4Vy4RUsvwmyRujoPa!-@J#uBH7*;ZI=WQ@p}Y8T(xmUr`Uhg!}%^) zd+Ij;I8aaN&fasqyNmU=cP+7>o+k5q#7>UC;u*nP_zoT>^eKK%+nezHcjUXkM9+S>&87CDf}654jY`**E-&@DT!aB%zuOBsHb864X!fbib^Y_ z|6u-I!R$L}e7T1U>zuEEUnO?-uDXqf{Kyq*I$nQY&vnyJ#J}e~ru$5+{}}Ypc5jdV z{PP`gTg>N7r_=EI=ye<7Xs zuaWNbKH3E8tAC!xYgRtkXZd<>Xv()Ae|l||`)56$0A9@h+x)e0AM9NE9Z&i_n)pw* z!-aPm*4Eqln@sxie)gXU=ra2rgTOC->VS9E-r=x#W8pg-lbLWxzk(mg!64pW67M5k zd<))5pIrBd3xIxgA7bU?b| zJrMZ)Kl`HYEn8eh+!y-ib^1YZe0lVb@vo|UP2xNDuOq&r?Ay$LI5mIAvCCX7yc)Uw zT_7JH3BMb_H-?jaPbj%xf_YK>#@{dOf7bTb&vJhq#0Q?^`pD;Vj9VAn^Mc(La0}hc zI`b#dI7^2GK&2brpXt1R$oCqK2KRwyl6qVhxc{A6=Pmv)t?(X0e86?`2c>JlC;Rn$ z^lasO-n$+%;KQBtE!~*BNzv<3WN^LY$?I%B4*sO)(w+K9a!)yf&O7;y@C)|OdpRP9 z#2(+;=QxWi1p10jI`@WOxA$26`xyty!bcD3O_*z(tHY8Vb;cbMxg&NEksq`p9~d-0 z#zGzE15JEyk3%FBB6b{^KiXH0j?hol?xAG(@!5HD$@JS2_1jLn!>cIw!+k&@J1!C9 zT;qImgROr%=dkx{+|j+}cX_UHPOwi?zMR-6^3KhJI45ey7rLb%)zNnt9@QQyd{pdf zt$pYL^Y1mkUU&3{_HhWm=f3yAyp4nBb}v(0qO*-hg!`7|y7Gaf9~>P;tq+PVRjf>kFRXpp@V>j`v&)~afgJjfo*RgpDi~q z?+_9i=Sb3jjXNaz82#DcpCxvTHv0wR3V^?VkH;Ni9OF(nzc=CziJsA}PvRcHi__n8 z_Df#B{9cbcB>ETe@qBTIrUScEo?e=}pUBZm^Y>p_b|=wq2(OPjbnwN_Uw^wj#qT%A zT^M)B$Im~I+9%*1Xr4VF(~bnYz1St8TzABm%CU!J??YW~+#%bqVE@H^;@QPp7rnZj z4i4qUC1zZkal}V3JEZQHWM9Dgd2xprzhg3pJ0$qFew99&FF5W{UR>iNnRW&6*Iatc z-Vgh@Lq5)QIDcdDn#`+fDm{#!bKD`u-|eJ}3AP)=3lg+t#~t$VR(#x(|9`|CnskTP z0{flLUuWE*yf`s=d@0`^eJ=LKmRzy;QO^^1XezK*$o`t^;OycKO?1Q^%Cl1>?>z?l zejlIeZ0#D^?=r%V0(J}8El#@jxI=PpjrIfD$@1e4<;7n*+qgsNIB;I>f-gV!`gZ>s zcW4dqmBoH+b|#fevmNJSv=@Kx#vKadBzp%%URxI^jp09tbS`Qi>8 zz&TLse4}MQJiE9s8JV+U>=Oz82`o!ZF(M z5}rG>#+`TG^3ui~^7c;LPsP^s>JGc7%$wLB1x~R?$~fEOuX}r@=+|Q3l=0G@R-nD~ zf{&}cRP5VgSDn_#K*0aS_n?B@?!F+BelM{4NCxe`ui8bOdsVmUk#M|B#l3m)_RkJ` zOqTze*jebmcAnY$;~s-&(BfwsoadM1`}rs@_!_|foD(`57-{glgZIc@GPvOvC+8*j z+pOmmD{3bdzDM}`sE@k~YX*Pxb9TP*b3EVB^3GjUwfw@{XupW+v|B~umvAg;&yA$- zW%u^6UVnjdvfs-|^`FLsm+PuGUR;oJ7vq15ANVPaTKnRE$m-{4;7h4^j$Om+{je=4vO!%s%-*yfk!$`285FBGmK;j%aY=$~=4IiIo) z1-^@r+CAct_oeQm_D|JGZ{Cjmvcf0)!sGA1OTp!)?4JHh1y|;L3V-tO{6hfGpY!+2 z3eSlIo(o3S+xdb!e`@6woVS?-E?1TNH|OEE*>j8kWPIYE$S2f~7<{`pEbAHT*Up{3Mm#j{) zyxLEWx${RC*}k3^%Kl08f+=@i)ATxzzpDAs_&99Q+hrVxr(owORi2~lSN#F@gpTt> z#@SykTY2+qc9Gt9ff-)qCE~1Te*9E<6#fM`=ge(S&nI!8BB)m!)_MnX>*>84-17_S z={-N3ljYV^e{DQJ5Y#KwwLgm&lriqi_y?|;+s|t=@#>;&N;gq0=)ZPS`yb0~Pw(X* z9$`?A@nqaI^{4nu;}re5Os@KSW1aJ8_rEGOj~3_TSWf?ky#H^vmGAcFZSx#YC4j5a zl<@?74EK132M8b6LA=~ONHDkm#U}d?_=+ojh3%AWKFgjD1^2%^ zzo`|q{F&jUl$<=XxXRzWpV~?9qjl!n{nDYz66dR_>ks&xARHBgqvGLsch2`eHhTHv z6u%vUzVKy{>%QI6q}8|iu8>|_$?F&4_baU3E7-2Z1sSTWrrWK|u-$5tbXYYxR(UM* zdq*6%@|2W=+05j6b=2AsdkEk!Tf27e#4+BNn<|fT9@BbJU{682g)0b0ho16)jqicf zI&)Qt2L*pKPj4OaB+E{Q3SE)bGrvsO@oax>`;DW$lpAj3_uBX28TxCc97DNb^5?`~ z@B)r2pnF#e9c%Q9k?c4oiSM>KHQ|sYu;l<4DHvH};e(nChyZ-$; ze%HhMJ^tM|H)ZxszyHDfs{DFByHY{rTPt3cm!BQ;J!SArwaxde5D4~P_^Wm4{JGt~ zO86Lu1N)>=#;uaj&SJ&nrC(nai$~LC^ve5N8S?#d&RYmy<91Ok(2s1tH&n3utcgB#73VRv4-tNKlKiV& zYv{Zbwr7-gQjKzc2T-3s`(I5e(@ z_lDj7^?x8=>^HQR+P#YSiJIyW@v_$^YNn_38~ptWfBeMR(K-0{|Ec{Cb(#LLv_w6n z@WVF#^txEC)QP8x*N3*UT*tm^Z_f80H9Y-j8~$XUV(WSQE5u8$;AIlu>0j>SQMkun zi~MPPH$PHuFZypcYdw#rGoz-@lCC@T`u_U`KIrA^kQAbqugka+z5IzT#Y4R8@sSr- zyn`N1ZiRTN{50_aeVzLpmACPkrs);rpKhqj^l0C2`CY7s`O$h7FUghvI_LXUhTp4* zUyJ)W1bSD!!na#_x7BZ0yQK#30X@**E15nJ{T0h~>|ZbA_fXFE2mSjv`qe`Q*CE1X z`}>&!Yzi~SeeJPsFP}faa%Pt~+Qsh+Q%h<4 zbcel7<#wLE3UbrrSt7qp?&M4&vF}CuUfW>!+CY5Ko`Cp2 zOXP>PUp-S_;cvswP|@^KuMgzKuZeyndtT!pUX{uJ6PwmjcK@OB3hBwq`Rw;}v8;`L z%Qn8UANPmYfAIbI_~`S@m+4PwdIbJWp9tqEq|fLw!|P?ltIETcUBL~0F-30+lV%_A z`c0`p{idNQ_FKmPDh<*D_4BjWw|WD)(R;cf_T0&$(l6$FxP89ebj7~co9TJSzDU=% zhb!8YJ-^Wn+28wkerM-)5L*>3_oSE4lmCf$@q8*WI!o}KssDn(UeEr*dGPWjTn|6| z8upVb@3ZCi69t=>y3zsY?g|^H(Ot>tt~-!Fx?i0K#(8cu_1|p1)gCj4@07m_-@iz@ ze5ajDB8hwCiZ=-ypm*CJ zruVsLPw&yVwzELKpP^^Jnd2=Q97QjejNj6J8uC%Jv&wau%>U(VC*UuxFv4*}7LTTA z^g+HG#$5pYUTW>6_&xTC54Q6OoZp*X=J{?hGp^@3;Au1a?XrF?Uc~#Wh%tm|h}@FA zk9FBMoEw8b3hea!4)T(Em;EM+;5+`_my)-xYVV13eN_3NZ|9HL4%%JOc3Z!fqLXOv z^LBqQa*Og?-km=!e5&9yU-Z|)B7R5JMM*FFY^y!3|J9`aN!Ewn#`-Jr+eO_gtY15+ z^}lHKUG(Du|7D+Y?kSGv2ioN!meUWApLcT~@5kfsJKAq7Pn|$N)N2=_b>`zfqU!zqQA6LK8@>zj<$xec=gr$; zfHsuxPvE>5@t>|1z7Vale$1-nr|I zzu5Wae478czFy+Z z+-EvGKOpto#nqFnpVB8ELJsX*PZV^WblP@%14!|BaNL21M!iIU`TL0`SRxj`)uUSK8L| zD85r}4vehx`^$>Axdg5Y{d!H&Ln~hMf?ucMd<leZU~|Pnc?b7%YKMCy8P8$G>y5a_I!wPy^}|Hmz=GiWCO0u$Z}a!O8TCwW zR{iM)*FW5B%fBVbKcVg3<}PelySn~wMLy~Mp;+4A==L_&SYPw+Nb>iTB>yhAZ_)fk z)gE*w@@XfUR=$0^>s>N`QN>>a`HY`Ds`6ly>sw!9`xYa4pZ?o^<+yft=Jxa@Ox9^A}s`Gy@-p3het0y~I z>E}N8@JSn=p0Dh50|Pc5ZD*I2TW~S#(du`r-&^~n2ekcB_tsSV_b2^duk*Cq?M}fh ze9G3p&huV(acaDKl6?9BKjbbNFgUe;k&9A)pL3E$p!kg9dqW_W_~~Z-P7YyQ~g*tnIBdc5(Kh)h8!eRAq z_4O;ZhGigv=lpGfq3(^vWfzZ^Rs+L$w*J2)P9dk9BLz9FV_PW?MQJ_7u?;zKEX zG?MY8@Z5oqO(7h9-b%aJUfJ;B_o?0=Bc;Cq9p$aY|F+V8)Gczib+6#=obikpJ~oGN z1moM9Q-6!cN63$hGbwy*J4<}*2;ne%Z1?r6h7XG~?EL!xDSS+}=%bjzOX4TmtO#F3 ze1=}Gr#WZq$<9l>f2X`NEZX0^&)0K!=kCpc^B%(KcC!7}xp@HheL3}AX$SFEvvHa~ zU*d-!eyvG4U8^)iUL^fjtbbjH)5zey&-Kh0oy}0r z+PUgXjreqxARfQ^uZaDP^^}1$4&yjGRb+YX| zYYg?tPaiRTWDfd>%-@_FAiuP8;!);z>~HRey;k{MVSsos|KZ&^J=$GL}c%0KAK=fT@?U*JOq=ZD(# zs-_GO@=TInw)|gi%YP)v-^`+fPc%}a8R#rF9f-?QT7dHI}w-z2#= z5x_N(Gmax3t~`7O?M%b&AoMux>ZC{S&rp2a@c20K=>1Ehd+qzZZTJ;CjN!lQfaM=x zKKT>q$jD-u#-ldw^gBmlFJ}M8E4=4=+*v#b zUB{x=aJ>&zPVyYt_|BN;JK($APZ-~+nEkiUzUw=(Xr}iUZG6AT@fm+U(YK~}JmJ;^ zkIv#~So~YjqZT#)m|G;CRgW4&N!mxkcL@%9xT@BF;=j6$>o~ISk-=NY@*5RBkL^#A zzeA0`-{8NW@EiZxPktGnSvUS=dix{Bug^e_{Tf>1J0BDBFI{iVXieo7x+z^KdY<~- z7tyV}&mn27?`9l_Jx2MM-|xrb&nkM8zz3$P~TAE(Ns!dJdSjrclIJ&SJ!^|4ve`jWE#g6?17mmh`a|MpH(8Xlm*{K&%ZJ^$YD{O^fp zlS}^H@u8?eI>i0~`Z4aeW%GG?zeV(><*-27nsrgsk&R@iP`?R)NU!SGP5 zlfH|zFU}PFe5Aj-!Ux!2P~H{3p89^GTjU@XV|M}gmz48%q;T5i+1k~7fNA+d1@pr* z_{MYKo6UtU$DgS1eJS`H#|gX*+c~DZS5q~3CFOh{D#|!;7@OT+P#=py$0I50hwqxW z!gX12fLwnh=lgpyzfYePJY|DNQo=J=IF-hKCLX{HbR3eh9+o5PRfBWis%wfKHsJHS zg8VM#pXc*yL4J+-=llHbARqlp`xp59o*=)6`F0;FUhWO@dzn8}Xr}0XB8RT#xy~>j zBUyMyoDh@eeNjW;a?u3K1?z7rsuN$z?@%sgc^p3~J@s-t?n0xle&fFvVXB1QE(Qs; z;w#QcN8p3ANQECBM=JInIVX{QrATOzU*kx{4kHqV_7>Mqe|3id7z!^AOpwN3Yz~uO zn_t%l>%!NkQP1Kg(4HE9@RZ2q8GJW?Q_9)+iz&Tt4Ef6M<1aUs*sj`*T>NF&O|s+y z-n}F|u*=>9cz*ZvDEQnI+M%5`vh!mEinDTkTJ9@|>-LQL#ZtZ?jvV~lMGt43AW5aY zudJ;p-;8+AEncs#$nSW0aZ$g!?jKY z{Rq8yedzZ}mfvVdIgkhUN-y(!8vM=kX{^sh{^tEz*cVRnchSznOLhJ( zsT}IKHx5S|fgA7CEU_H@f^hFe)=kY#BE1f@FZj3)YeDY&U5B+Xt!debes~?$3=X;> zmx@mIZP#H<$vx)lK*NIP>wq>n|CV$Pso-~(^8@mvozm5eF7166`W2v{=q*WHRBta6 zeOA`3=v%UGWuGMLR`gP#chU1@{rYo9vE#}5m2*W|k7B=*^(T6?oaaA+6z5L2?Yi-I z(NMqg16dEk52(+%J~_w4Uu5gPz;(Y|T{L;8>q*!}Q}NliWZ%1l-X;7IzsD!-dyU_r z2Wk2K75m=J@BQ|@=ahYq_?`S2^M2aCo4=6pW%Dyg^fK3Fc&){RzZ>|zZP(4;${eTg zIl<=wQqKnkpJFE!e9C#3@WcC&3O>bdEBL$*so?Woq=HWwvEtME!A~%1#h>b-a<7W? z<)U8D4U|CORo|D3x{-oeM>V8aOrXz8l)kD+!9=18QV@^Q8ybm9_>Di@^N=(g|54*R z3m{RUyLqlpw@2gwe*5>(?l(PjSB>BA>t=dy57T>kktTFFCmmv(UVaEa7rY6-7rad& z6}(L%6}(L#6}*ij6}*ih6}&Z(3f^`j6}&xww4Dxj;1=R+gu&r`k9PficfT*cZuHcT@<4``la%^OGqYUP%1O#{ zW0~cEY;8|cmh*8`C4Ly;lzuTeT24}y<9iOmH`~i$GPRtfQm%-&gvNI?joh@R4)6CU z-$DIF>!+&t;Cj3p_|dpz;ETa}H9gFA$+{mwIX#a-xs%q8tzTcRCgnD_l?&FhFV`*Q zHno*IW$pD4E?=%k$~D@``SsqLF4rsNWGANZA>n$rc)d>J{Jvat3n+H#x^`{{mgv>^ zy}ihE8>UKrZ^hI~x&@>l>D?e5Nk>3xl5Vas-DK;pVe9WMu0QgVginKy^hx=edjP4( zsok0aiJDfNygzdt=t|;4iky--nAZutXbL%{Dd*-i>rvLJtjB4jvL5G< z%6gncD(g|!xva-oq%tovNM+qmA#Gpx!UtsCPavh=gCEa53P+c_SJSGyPt%IqrD@sS zuW8ARYFc!AG%YxZ-zai-K~v}CeUj_Y`f;R!-(y%0Ek5SYzl2}Qcs&0XKHtv&@5gT$ z&oQJj9^vycp5sVmJiCwzA6q~wC(AmgCk@ezvH*fasD!+(+kPink5>oSQize}W8EYCh^bVubKhkyY+{So~^Od4o zSx5f@xdVN}+W8phL&vS_XCr8U?fG}nenG!CutNCV=qCQUeiusNcjJF(nRbwmtdlcX+;z zb))A!`hKR&7uz-b#UY(;2OcmUcUvZ0$@+1(XTp{66SplhU+_kG$oC8I6TfBtR*etS z4(8$xx#IZ`@TlWa{b50}@$(>prc87t_xG$Dagz?@rLW z%u%eBxC;64rmDi{c6KoxsWIKr&2)1Q)2*`q zKz-vcZkyn(O)r%FgN*ZwDZNJUK|K3-{U1!_xA5Gw`sw_Z-emicp#9eRoVEI2PPM-y z>0kCW0$(~`=85ph{z%3rzhxc#Zc3hrzC(JF{ZxCoucXR}KEQIa&q|du`D5!!_v_3j zU;W(_Uln=E^@(Y6KaO9`c+LG98ed)${<{VLBj5D-Q>pwZmCx6pp4b79Xk<;#BTS>>xX-uU-Ye0drc0oFIX#a~VF<#Bjit}5U4?^Ap=RUi9Y^ar@y zjPbYM$NE}lr&$F{Vm(|cu(g~C;1-l>3rEgvwoBG6YMX>kx%;CY4et@-%RRzeI(7FaPH{a zP4RE3CiY30ck->5iZ7WTk$a%P^)YVzsafLh8RJh=75;Wr(;K_UzvgQEK5P8R;)uFQ z<4=m$HoheLPtcj-k^I;8396TieVq7|{c3(Wv7fV?>~HhSiG7{r9&Vc-_M7PA(0?CB zInMJwPk)eWESKz)P|i8NM+*2cUn%%?e>H~^-%UI0BWO?T7jiC;wqM+*_K4Ow$S(a| zv2%^GyH|gg?7XAs?$_V2$7mWk@i)5;t@6AC`+fWd9^o&*)SrtVG&7(*8X%4+%XS!xzDq*m+3jE-tEEbp2vSJdW>T&jG$` zO~ZGE^4iXoCEw&XbW5~WkbTub-(LKm;ExABhKxaJH~zDN{%+mhvhQjC3ZXBto5`P^ zUy50c?H8r}L-;OsHW&Xn{Fm7N25tW@iu$`1S0Fb&Ne~ZB{WliSuHXaXtuEPk?Yzd@ zVPV_p9Ivb!#b5kQoX570cdekmpJ%*cN2Gi+y)ziEtRs&1TLbH<@&4zc{(gbuoo|h| z9N3e~)W;J1gMM^=b>HmbzfAD{jN<*T&I<3c&SgExpV_xT?=8BK^E%@3+whlf$K!vg z=u{a>P~L-# z|GHF{{@eWKN7!G=uNckxbp}2sdeYwv+kVj6`P+)=8OGQz98(wdRJ9+2(@0+%-HvfZXyNphjmJE&`Ask%>N0-78|3lH>P43rd zTn?N&hw#4J@ciyJJSYB?z~`xUxZ=NGGC1wN&)*FgeCl`T?aqP-J->|qW}vQc(%%j1 z{PaoveX#em%A0SkEb8yQ-7EV0+iRm7S4?}xUv$JVqd(!_7PQ^gdMqhlau1U4f?u)U zL!c|+m)_6!o2Lh~{fDgo|5Dce;$`ngP(G~q{LeW5Rl4zh5Y_))zpufMR==;T5YBGK zUHP+KgXi>Ve((K)mjCPM6w8|*f-9cS`HJkkpZCn+A9a#+RIu~?+9H3~^qv*?*eHKj z%MGFP^u0a&E}mw7v6uP1EBxK7{s->i9+eyMU#=)T?ttO@uW$~nQqdj!Aryg*Z&=V*%a z98GbaqbbgFG{t$2rZ~^h6#6w&_{9%!oIc)5rOw|K^NUEw9q9C{fIp?~)AfA^oTnK5 zJR8v09{dI!0ngTc(e4*}xfJ+2dH>qwVz56hk{^rzgwQ4YG!>t`#|Hltj7z@Hn_l3$ z=$BxAr8th^Ip7h_r^`Oia`F3|;^mruWARj6G!i{2{kg$t#kscckMVmnX88{U`Cqa8 zr!4;`B_A{B#x^3wUkO&9_&XTSCYW{Z$HD!z@a31kf~InQgg-qWMOrm~rZ^%Ux!;iX zhLL0Ky;FY|^*4kS^%m!T(&A6*Ju@gjl%u$hYVnQyeahl6={ezhIrk5vf%LkJEM7C_ zOX;B)HI&}_1AlY%>&7H`IlsZ;U{X#v_ugsVhh)6L_=yhw@(9Ka<&W|c`>;{=*T;3; z`96U0ix95p>&Cag-p02DU4qvV{-JWxxedQTcubDvw4e9x1K_ju(Wi0+?OoH>9+Z6B z7r1YKhu_WKozoB2PYS=dr!gAL!yAPzz~8J~av$JE8}E&6<2|o!obPOd3-{eR=2!8U zKhD_38T)bm&u#5tv4P^1j(fTVKIi`NY39?n7iaZ{a!UdI8=krh zPv*DhqVG36eSaJML!KI3{XenF{taL5GrvVR?Y@V*;YR*8KGkFLr}PfHr*7jpuduG? zVCT8@w)++6reDShu5Wt%6VRVjZ}9p~*OxfHUZbmCp)1T!p}}(Xx}*U2QkH|=Sov(h z=pcHFet)8{k}lu%=B)C#e_;JY)=#)^@%IO-wyu-=a0(GWkBrnZD-r$>{aO zPmkfV$K&&pMlYXiqZcR@MlTqy>KVlib<)iXl@EdbhfI;nJ3awkbw zx~>3DDUf46o_S9R;}X6m{wnZN*<4W9f*V6@U38$M5Ii1?vxaaZybF2Lx87z3uxu$EWSWZMFeUDsicGobgi?eecOtyGh^YJ-WhinEYPuGrZY7yq=$4 zWxm%}D~jiqeWo&ZmiXSra#h2(zsFs%IFdfTq~0ThHkF3U^*?QJc=_3Z@1J4&-G*-; zZ?2?xZrOKAIdJrR#OD1YoOioNhU+`Z2YR2j`Dg#4M(g)iT{}Sh6zZ^70@3FW^EdSm z*Y~K=tKzqe60|S7uFq?K67N!G)w$1Wzbkf+vva-gH#`UHJ>*AzzZvio7rjU8KT%vG zAMyJi?$bN;(B-I3x{bI`?vT@kD+y1@%9Z?n*Va8om6qR(|I~V?^?Ht%d>(vk$=0jN zYaEpEod}pe1h0rAXzg{I{CdFTqvr#KdRi|iT$Tl22A{IHLLKmC#ks^Z6+U5fV((+A z+=tYRnlg`}y#do5+;=xV>jrIqV06>9*XY~iR~4#+$e;el)=5uhPwU*bXuRaSxa01; zH(0wyk3Iil?fHF4b&+^W>{vPaamP9`{#my5Tqb{ZrFRkU>F>ZxKqu9`hR3~JZ@eE1 zebDIB>x2HDU%Gy0oZe`V{dUabW`2*2eqz#36s={?S7DpMwT*Du{FXPJ;yjQZ5!cA{ zWj`-xi^q$; zd`^1BwUR(i@;yw*>lLn#L|)$`^n{h}?*XO8jq;|C`0}~$0Y0kj3_#_^I)|P!O#Wx~ z`DN@^lz$dguY=gX#pd-E&MWoUPCH2PAuIO~%b9+SYl6y8ia%udw|!uLh2JX=HQVPU zgHOJi>nOeNM7bvMlk7K)pQh{&z*l5^q2nCK&M(VV;uZG6YXrXqy`Kg%UQNHh3MMPz zhpwV{M|<5YU#9&NA5ia6DS;i>PW)HSqwhm1=goH`6+7b;QnAy_Ar;9+igJUQz8tKxYNzw&T0rL(+TPl>1^Qdj#KYzg$!~g?)K@d$(A7(;A^b z#(e_%iLQr&l`BX)o0E1HOfJ5yD1LF<+}n_Ydcc#FPu|-U`9F?a#ea~0v;U3@C>Lc|%-+vI4d0hv&vB`KA?NI-cNRa+eo}Uin~*Q-VMK3w$UGcV{62tx znV)BB-fqBmql1yRy)gVmhQEYw7=5J1Bi#i0lghtF(*Im^iu97g+pWka-OD{NSsyMk zf5}w);>T?G8f?cG;zSltv)vS3-H7sB&(r32+wb5>w5Gr__`OV`oSdk z)ZE}tx2+@ik;23&^fWSJ`?_=G8!rk@@jv0b&JC_nUfcd*YUFgge%cYwFPD$KD0pTQ zczABNLnW~Ak4Fbh&oKB78XX=UvF|S{-X;?`EiRSYIyjKR->A|lxb(>ID}cu~zlg}* zU9H;svvZIG(IVGPp?zIqSVgf*Ux|NkeLf0(taJUspDd~?-MWgDhdfKQb5&B`1$;1B z-`&RFx`n^NKd-pLetPYDuYA7(Ig~RaMdLTY`QUhQmGx5V|8msx>;HFNpgb7}^w4wR zmqM{7&jxu;NWDfa%is%_AfM-5K0dM=k^R#}_%8DJVx&CBetG%UL<0Ar8u?Ol@MMav zc`pI}>m%#00A0@~@a>9Da^6$&=ThWTF5GAS3QaH1Q}lTm%4N%+du-g>i`IUMuHJ-t zq^q69b@pE|de!)+gUILoc{j546`;Ez^iR6mHFzpDZ#ze99$!{IDgKFsXRGHosrh;% z@;P7oq9x)pHBWWqXV259=NBpbzaHg@|IJ0^3vP3t(s7`#zZ&H{f0%p)=IPDopLp^9 zySpGaJNfK+$@q78I!@sa7DB_H8)W=-&rc3}`t3LR>?fZ+XmV+1ahUz3_$`_n`M+NQ ze7q&W$AJR*&i=uAihds{@%#8l;}zgL_b2e(Yw*oQb>go)K)u%dMz$E=naP|lH1lYB zUwpUmn<>xFQuL4MsBqlVT}}3fb)x%Wo2_d zE;U%Lp(&QbHkPZV>`(X}oMWh+2$X8;=ONmg+{2G9a$an||A3tbJ^0ZTet%iv8%yB3 zS>Y?XJC`g@xY?<0(eDL!SHr%OFS>pD9e(z(U;h?b!MkBNFDSXKE35o%=QY>rKCtG7 zSJ(Jm&+Ra1+fQGo`4zVzm5;_PU+?E*Pq!nLkIjJPAJqE2?no*h#AW$|nty@2G?jmT zlK&>n?{}A_^1%$PzWVdSrgdK`U;OB358%C+!){k9|H35y2IZ@ly4!FcOYx-R7x~Eg zH);Os+#RWW(exz$wa%SfX8dwJ2Nby}`=ovD-e!~Ssa;v*H2L}Ex(=>$SEll>P4chM z{43p6seI7`S^r{PXIHp4G*7m*FM2BTFVg%C?vhl#>}Q$(YR$jJy)l)4bCUmh&A-Xj zQ~6@okotGI^O~pI`W5?}e80mDG}qg{i+zUuy;a-W;ciW}w;{J6Wu&pU0%D({IG=-5blypN&Q}f7Xqr{7M>1e|VOu(A50BuE~5m@7ZMeduq(z z>>NXT|t+h392O%E=7BfbHb(T~^-wA6#jc-xEKO zhxt3*F7&5S%AFF=4@w~%Nqsw4u3CBekGJGY+4}eAd;O{N1#ElD+W(X0e|U}IeWF6V zPk6lR`OkLXjr1~K9#*{1yJ6y8{Ve8?PrF&AXn2F(^Nj3Q&0bc;@2jO$h&QySa#rnc zn4O&Z-K^hv&jIHqx-V`}z5?B*FQnSVIl1AZ$ocCH;jsD$)5rShd4c+8_bf`*uG*{j zV??Ik7K3*Ul%BUCpL+KY`*XW7FZ1}i+cnzow@EOAbpxwy&iMNbe{l$RfWHfJ>Ysm> z_z@_=FyJ_aH<%&BzYFgm*!kz{{CI$Or6=p>HSEW|*5?CW z%@6v$)aM8F-{j$}8eRDF&(aL($=2Cmc{Gb2I@akv-;amG>R;yT=dIHeUCKET>BW_U zds61l^k(+s_Fz0hM|=D_RDDtG;-tqbJv_mc9qtjG7at>6hL(ik+9F=;>-8ubjW`Ay^pyR~(VV!&? z=zm8}{Vg8ej`Q=aJy98gZ zQCd{J2@ZGrX-?w@B zea=hw3jF1rv$$+-Q)Nx(W0PCse5k&-6(wwcQ97;p+uZ54`D!BH&(~@jeq>)k{LC5r z>31w1Nb+6uUW4nsZS*pgYrd2BMLOu%^v+Z};%8xY)UL}7 zzRL-p`G=kvINdhhM-q59lujw0Hn>x5ctSiWu6NvNz32r z^HXwbvekaJ`~n`=+wjDV6UG!6i=bvh$uNGh1 zS6I?@ybpZ;D0<%qQ>4%{^(EO)5`S_pTE3^>|7qD#=1Q86cUT%Jx=7?x3U4w@^QR5J z_m3OA_D+h~1GX4Gwng=fd6xG6N77kp{1eEaew^+9gZtf_2RBq`67D+nA6K}>+B5y7 zWco{CGW&a9;D>B}v_pkcsdjgHy{MFK_x?gd#^r`er&+$y(0$7zCH)TnOe?5Z|oe6DJCtB&ti85~y; z4(0!{f3^Fi)d|a=Xv>!!tK}DmEPsgk%3nnulE3aXIQJBrgsVDCxa_{)?&3PX2mTGN zD>z4?+;!XixY3^SQL{_kTO7`8f4ZpnaLryP`EICcbYl3r&*Q7d;OfzHKiEkOe?`Mz zWkKm=F6pOf|W1oX_fO;Y_Po9^IR?Pv$p%j7RL|kd^NSs50q@32X>5^lC7iQyDR+(=Og$& z+GXWYPvI}wJ(#ZG9WkTBq5eAISYKCu78z}(&Tu{Ht%ImHv=qh zadq}}uPB|4N9!!#XZguHJ;q-P_TAslt-hW8M=NYE>TW1q78d!tKsuhl`V)E7LEqKC zV*QepFL}N}|8G|_zwd7Pd(Zbch4T?xR}dud|He)Ze~7+?|H^)S4ynW;nnf!1j2Wb2 zx1UBT{)tmag^x@km3@ZD9npiwk&65oLn?fviB#mN*hL`clXo;k&WhX@ek1v^uaNR` z{w(!n-ze?*bC`SaTly2tN&4T3RN&cxRNxamO5ojw6!0hgya~UhpFyP3PaUcBBYsKJ z&l{0SKVtcnal9U>^z%BT($8y=N`v<|J=+A=3JxB$Q;+G?MlzpMlwR77d!nY7h3Vb16 zip0&?j^7eDN8%34dp&Y4Ebk!5`LVo%aIdCCcb}#Ow@cH=NgVd;(5}S2m()dlN-Yw< z{%ZVu1m8Qyd94&wcsP&O;J47tTae0l-;7koJA_omD=o=*uR<#0y%MR6_X?yk-pi57 zcrQaLTDbu(h?X0xpue_q%Z)zpbz{a)D#^v=K?>FP)Fz&T+?Xhv~wsDP;AK3dx_^9K+M;!+StMiV*>Nxt5 z$~Xp)$~ewLD&r8p4jIS!NM#%sAeC`kh*ZXL5z_R$U#j!fMqx`;;zuN?5@fBo_okuG3K4%0g*5qiRVkUBq8_?OU?a~JA7wfM?KHeYZ(f;?D23C4L<`H<)*h)X8(h}^PU zb3cJJiq~}Ynw-Dse@FPQjrfl0Cg-J{n!6bF$QH#-Jof~-DK_EqG{xA*0ktu z)U@p0s%h12(6r{ne@wW`ZPxy+_??!Q9)IwsYR8+Eueep>t#4KEb~7|O;;T>ah4xWS z@FegHU*Di9;MNrIXbSi>1)Q2<9GU`tO`W^N(iaM6HGmTwH)CGA+?3>vNOosA<`a zX%fqN6yZ>RqA-1Auu z@C1!7HJ_t853P5r_>L3gw0>{BBgA)wAgA>^oieSTDk$@E|wx^=V|9i^Z z?A~NUQ1rdu|HHl~H*=?*IdkT;IWw1^m{)yfo5xMT8OGZ_enL+36LOlLkkk6iCP%B-S5P&C4)p$E)$e(qC7mCkobd5= z!jEvjJZWK}_V&Ejj_WVB+pHd9{rMwQ4@s&XGF;?Fg%+C&aPZSn_@K1qH9);n+yYCrMY)X{yk{q!D9iMy^E z?-&_KP@rF}C-)Xq+5b0=-XoCV??<;dAKwQL16}@Lv?KdSqzCIbOZ0wE>ED|@d(DL`Th-Ko&yDA6y?m}$4c@bj z^FhV?gK2mtdxXYG{vpdReiC@)d$_o-qCnuZ_YaP~T;SyRd-6Ale%eUao5^MWHTFXk z&WMh0Zu)(ch!?3czAX1TIR%1;U?0=&Ln~bN{zxa}$LIJ6vd45DN9%gfa}Z|k_A(B8 z{^4(dhx~LFo{edE#Ot|wU+mus515p~(^Bm~GrB}^ zW{3At(&8evVSeZ2h05oZQF#-Sbx{qU<@;|C_aCY!_X(Mtto8U_W8>5h)vLcw;ad>m zcYMAXc6O+Iy$XgeFKa(6fAM+kP`#X#_Ol|?56cZ`DWURyg8R{XVynS}XeEvps^=AR zKQmn2hjQ&c6we>T2Hj6j`ijU8-kw ziNn(U&uvvtEvT2|`~s7de2GiKKD)-1$odN1i-*M~dIkvaYVH4O>0jff>P97=s;<%a zE{>x!rj^pel4%<6r1v>4NokymtUr8jr&z|z=TCuTEC1aM+TRB0kMT_U8SACc`w~*m z?xVWhwS4!q@}yUY9vg8hc;3igG!pYAaVsR3CBME~&iVDRt}FAAU6<}J0F%&um&D5? z4#%bY7coe!pNFQPZ`WHAFHg${Q`3A)w@c!R53Y~N(fn;yZwbeN7pZRaC=z@KJ=9xLq4TZxqA%$;O5j!g+A1p?J7wul;mB3^auq(mh4$Y< z`qy}mdJvt!6XIxeUp{W*Xxd5pIoeK+9p_Eezt6u@+rLxV*M0T^mJh}>cT?PtlM`3| z%k9>i#e9eCk92<}{E#)4Cre4nM?fclA9Sm5))zSKJ=KoO{lT-oj_Ih3>6Y|up|ch) z#{s}?7=o+r3g&Nh6#k|bo;sm&B0s8$L(+J}D)_T;Fk!rg?(Y^phG7!DlYJrZ?hJmn zjK&+xlhR{E+l%NtIYVWhfNjV{RJU<`x=)|+o;aek-^hu}Jh>sh?Z$x*vOA?*uA}o= zZMoc_9Kr@84pyJnL z8Tafg?>ou+411aMU-8Fphj@~7__i|=pDFh+lD#bO)pr&MJR!TNCGf=cMNY$}O3C;% z4mA&~4D^eAnBt7_`B}!dt;6#Zy1#|M&~b$D5wqoEN(X8eHFiX9s-6N5gUHV^Js*^> zdg{*Lb4+guiEE4cI-k*m&=p<~^|bPnGXBte$KE zQAg<3G>O{&l3GvVrA*x)WL&@xuP>k09r*{;e^P&eysw|H{U}^$nETBymi&CV=ONd^ zpRrD`pJU%=&TADv%Zx~cw8wgiG27XC^|W8T_q3TEkE7$k1<^ErE!;SdyQ$2tX%yx4 zW$M+d;P)mn!B9Elok>sVNeSss&bz@Mpm-2Df%7ISB;R4YhVBJKeXpP7tDk6wukib% zXCXhL@C9*7Wl}!M`W)1t8w8H%G<~oB1dS7F1yv1n0=JfY7Y_>rem2wXFicRV;5#&? zTR*%nrgEG6+lU|PxS@IpVhHwEFYlbi(_^bToP7GNXN)^{|6TLOebMg6rTP23c6_g? z&-|nAEyY6)8?(A&*-roNx8K8;TyoU$H(qke)x$?GpSpMa%BF8$uwnn`+cQTm|96+x z2i(2zB=boJR6 zyk0)>`+MJ>HRG3h{WsD60Y9F%{UcXje&wle&wqYJ^Um+4{`J9yLt8CBW!a=xW0!t3 zaq#3fDsEcX@w1agzwls_`);_d{rMHw_n6RX-Us_n_^ivI`G>!?Q?&P|yEWRzoiV{b zc6jq96W$rubZ*m0+uixvQ#p%IoAmPQqh6kI-_%wwo%rSf^B?PT(M5O69)8jFr{8#0 zmoZCLow>H{W$vRh_Z+;(KeS5Fiu=C0e+nw_9q$fXpwQn>sCi&dW zA3rrXFKgb#jy;k2etv|V`7`=`d{ zZ)h-X$5-B)_0|O+o%s7XOLsDB?>*<{lRF%~u=LT7m+w7u?k{^s4oQ6R$t^<;?fuZa zdKbU@Mb{S}KmW*U-Z`*g{fDRAGjqcaAHBWog}Y}z9bLNJG7*? zI`Y}Q>O?kNe|FzfI`ufG*t8wcIr;SZnZG}|f5(Sz9CLrWhdXS)a=^wTrq@5@(vcm% zTfDS+qt3gvG5_qa{dIG0Sk~~p)zPuWGdEOt&$K^f zRo6S#PFOkS#^JuRvE!@Tj9Yk0+sEcT^6bfH-SB&t-FG^FW%;QGHtNy---oW9aLjSb z2299n^WJv{eDe0;7arE=*9+>M_0WAeJOBF!Z&HuJ?;OAXkhfZ0aQG_^582Rt-Zd9I zyj$HBuew$-H_}5dX&-~=0ZtXfuIp_IS*Isp3-hHqC>w_;pz4(nb zKc3S2^)o-)^_BDHToIe{{wq0$ngb`VJnOBeB3}CwpS$m@L+^QP%9I~ROz6;XhaYeH z{;(O>PmaG{_Rx24cJU_X ztnYo%_w{=mF{y9kr#Bmd^SX|uqwG(fB_rjNFTvpy> zpX-kqcj7+}|Dn}sQ?_-ET6@SL{ck8Sjn8ki&ym|7v)v05X7&1F!^eAm*6fyp{KS}U zOJ^^?a+hJzmly0>cH9n^77w|g=jTtIa@(Y`lkdr2ea%@P{eDN&2OhY0#G=gMBGvm+ zCnt5F>tABx<5QRW^{*&wTlc+3+dlRBw0h-BQ@0g8J-%q=z^7l|ey*Q5|DrL+I3?)$#kwvj6kpr_mmx250@} zvmGz}q?zkBt4KNz{`=u3gATgzr6VqQ?}R~*cJBAwRgFH&znNqRe!mx8eCml`jlHnv z@2ktEHtze&rDwb2%ZCk^a)G~7{h^P&J?N8q@rI-CsoT5xx@X>OJp6$hM=YP3ikS7= zez$A)o^^LTx=*ip#hrVzfi!>3jd`|R^7aE;mp^*y!pD;9S`o&=AXAM+~u{lyEpjt%+j|m9^dWBYl<$* zyQSmkbsP44d{SPW3txToq~~|KZBFlFlgF>z(B$B^m-Sg&?2W#n`MM_-%~{{9{cqoO z_b=Mv=l!~We#fC;-e2(KBQ5Uh+UeHI+K)Y9o2T>d>+{ykf;*0E z`oQGt`@i4e``H6lE!%NLeRs~F<5wR$d;5Fa9MY%#nDVk2cNV>Rxp(A}>0kE$uKt|E zu6T4=$&`IA`gEV7`lE*rciry~J!_lR-7aYG#8)S*n-ZV2{M43bPMZJ7l=(AmzoB#E zKC#lDnjiPnD;F$iTlfBPi)Pe$|H_6NJ{vk}U7v2N`WXN<5!m5|4`?z zD~|c#-fw5!cG!7?e%NW|E-l{u;qhgAyz<1gC*E_(z*bGitmymhf_1-+ytQxlS@kY_ z`{RxkLrd;Cpxrys1^XPj``KR}_4ZLeuBbQT_45|J+5X3N8$Msvd&;%9oj>fPRXz4^ zed9l-p0{j+Gr9EhUk_WjpzmedJo)vN;|f3j?f#Pv+VRL8hwsY$&N+}xt8H+AuV9~&Qh*NpB*A2+_m=&?Jw)1U3q=!3i$wteJS zKR)G>oCc>&zHD~gHbZZ@qr>uo+=udhYWT0$mz_3qj{!}aAGZHH7heAIxv%X}c;K?z}@bZaG zUW&B;>7!dON_D;Twri$ec*0YAJ-XlGfuGc`c>Lh)&wBZYnZvJb^ULVu{ap|J^ugc$ zGkE^7r!-#r<$1S0cK5w+&7XM3!F`+M?eCq|yV(mzjQFJa6}?A}AJ(<=8<(7a$Ekc5V5}%*SqcZu(vK_RXt*-8HHF z1Ml*>?U@++)&me$0)9e;4w%ddLYJ^#CZymH#b>kj()zUh7HcN%xm zgjD`+D<9mu_>My+-hSrB%Uk;YdB5MeyG`m*fANt+=I71d|A1C+Zfx5B^1=UHaZm4m z?Y~WzCCNe2CF>tp)VRe-Gwxq_&=KFJKG^t4ZvD2GesD-pgB#qnlP)-aP?KjTEc$ra z*OxVV?~4_4$%+pD;^{Axe`I^CPcK{D-(L-=yUUt(|C!Wzr>9PTYnKNG-TT5zH=aBC z$``vo+dZ%Nwf8zf6aRk0yFZ+8V*n={uHC?&CPYVkkm+F~zeb;r|$g=W}k{L97R?zc|HB zxbNHe2+#2;`n|#{=5otTG21J<8Z(RPKdfCHa)v-ZFn>KE^| zT_>a6ILdeBS_OVDJmaW`r&)&gC9oD-!M@rJ=~r}I-SB}?YVUPTKd9q+BQ&mT{W;Pf zdUE`2&`wI>e=F4AtJ?k(+Wu>y_IsPqJY~b1t9bKV!CPfJJdPrZS6}c-aKlTMa!di! z0d{s7{{h^tz)knT1Ag~&rV|+Dpq!TY*SPzNooxzz+7&iRck#;k@hKYkIkZoHhvyuV z0-jB|!~Qz+9_Do^`Y*r)c4c~o9}Dvd4=c|nKCjAlvC=WoX{pSw!msc}LHBGwBbK@n zPIP3y>#Oi3FkY^2I{ICzfh(bKfgBa?xWMfy-0Gh-PD5uV>VK&4!_7~0Wn5i<-}pcj z#sSYEkE1I5pxZQhds*hmc}vH)qH^ANK89?m`M31pTlz@Q_z16sgqPlyK7d!^10nh_ z*9m;7sL)H0&Q4NI2|YH=hU{c&)8}CJX;5NBcx43FQM3 zO zBJn3oKJCk<(T(0K7kp1Hnp->mf^ajDC$7CuNBCV8J{ihy?Upg#zReeT+tXbjX_dbT zU-^++q zu2<e|=#_h)={kUBd>E{gtjREJSOlQ%E%wNnCIEF|4y{TSo61Qh?_6cy|G?VlP zmnWxN+WL_>k0~#qcFakfpNvl9{)=KdPBTo}iL#tC$!L=Ej|%1Q6}9D` zx%-?9U57(?eyp7RK>KM8K2CIjUU(e76Hn*sB>p+yaV`H6{VxVQ zKpv;@F}6?A_?Xr28E+(?N{Bxtuxf=4_Z524bVB*0F`f85`c=KZkkf<(s^@tEpWed> z@sJ??XJ|#73VOTG;xFP0-lMdSjo*nIEHeN75sT20ncIUic{8he-2i>3J{Et)vGq zJ+GDdbX)7X_`*>1d#@C?(P<9i1{bXDJFP2~6J-p7t^2<2)3%rKy5Zmg7(s&9wo zr0``2c8kbKr@zQar@zQar&Q%+W0Ct#G4t2wjL1Lee<>Pw<@#a&jq(*B2@~ED|C&GJ zze=++@tI7Pk74=prmOnA${AnFkzCjolU50b+H;V9wf3K-^}pGEv%lF-X7z-Rls+S1 zP0$MiJpqE5=q%v-Tbdp}7J76+#}4tKupFg0rmlFORgU&@e8>va?xb`s^5Zv4*H~wO zAAF*rJfl{QgycscfB5_2?vE-zjN3@+8PW3sJ4pQo#t}UX08pF|k&V(xCcT%dyvWLL z9I<~g{IF?sr1a~7m&kgz`jPE7bDq4WrqC}0IHZ>n{?g#n@i4sJwkX%87Ui%eDgMd& z0?55Us0|C%?gyD~ku zy3MroeEmC)XG;Gz`wcwM{<%5N@=X=Cu!M;R^%CiIN9HDfNl%CWKI!L!i^A_}n+6OO zzt^^K)pAVaVfblW=|!K8{66`plKjWkE$x0@ogVQC|C+eG>6Ale)cxk>fv+BMO!2AV z`luUEufOuU!|&O4&2uwGJhfo>Z+lLicl|DRd^`B*Sxwvhc+8JH`CL#_?z&Z+Eh86-?FyNK7aGkc->dK%zJ(KveduknBP0!J>sE7^G@qA z?YC=o8GO@W2cAEAQ*DLvGv@o7m&Pp(tKqd>|Qsz2AOZRozub#K`gm+%qXRk-j zSs91Xz(1xo=CvsdfcSmFB-k!fxj}TZnB*(`FzgrGH*??FJZ-RcH(`b+TQ$Isx`;d=p#{*jEpY)!~1^x$>YlmPTi|j7sbH4`f)Aw!Z z9;y-&YWP3x6GN(KyS&dFv>Qh`-~<0=?lboa)!$FyzC*{gzor*z`hY(;u7k0L0VlR& zGWXFBfxS&S58z_IT=*Msz*l2gQtMAcM`J+FmMTH=qdX9s4TY_H{D%ck$gp z-ruDS`mWf_0Bd8vfc2B^yP1B{RAiL12=EM2{G=c(iSA6U+9A_()&96s^|GUQz7N;= zR(sP7*81kJwKq+L_7m8fz{fuTZpts`H+`m>8QgZt^EJf^Lj@NdTz z<{N)I-b3Snd}aEfeeP`mC*UP|3FzVv<5}tOd5sUU?b`XH`~5JV0ba(dy&axsqC?mm zsi0aQ{`|6ni{e&f2AYtM`+b zF9JSOD?f?()Q%Ir09yAy+xma8pJZR)ndIKS)%!{8`or?9mEA^citIK^PqsdKsO688 z7Zk-Z&G;plQZrsvpXl3a+f~))$Xn!B)Rh0!M;jq0v;Qi3u0AJwF1A_s?~hoM?7!yG zdCjxuLg23XX!tX)?(a6cni4A4MpZs!dV!S7B&|YWiWZZ{{zaUVc+{z(!Nus4L%{GX8Pl(dmuW{5SQV zs=0{fw%%CrVCO4ljjX?-^QUbWJh#(|t0!*1<1TI9TJ!72AOHSKujlSP46#H`_rY)X z?{n(z;~pCJ^SB?sE_33sNb7KsZih>iy0Y%Ex?>|IP}lXKGwo1^iMpzC7VO&LLVJ@__W9 zwDKAH2)$>yClu+>|`)Q{zbeF?-Sd8hgkSTpF&&>^WOljHg>;C z?w1PS@$LIqiNL}8_%y$KpC;PjeWReAq8PzJbRhR_<^t<9-n4Nt9&*hB#ykAr8nD8yZliM$ALC3N$nq#f1l8k!r|2|=lv7hlP%+P zX}p+ZP(n-U&!zYg;yaRVjd9buHSP-DpP8lewpV_f_Nwu#%XEOSqht$C*Wol|B;()I zd(t_B>H}`2i$We(>_ws%P|+y+elfq-_JaEEZoD6K7uw)_tZVA$XN_aWcUal(@<3N; z-$cM73GdkdEW;CeiZ`nVp1T8r61-Qv-Ki&O?{~F7j2R>2sH7)?-#tkCB|b3%^|O^b zB&{6Jd-d%(yGXz8p;|tse99P(za$%;@!)w2+=hmtNQ=$Y=#~8LQ}B#H4_-%LxP)&z zZzLDkp6Bv3U$-b9wCfjowBpZx$B4g=i1l5M@abTj+^=2N(93E3=qKB{zK++&tHHWv z{#&`O>nWcxr*k<=uf`q7^S8H0{EOO`^(cG?afo%}Y)2|w25>VRdEjF^qqJQYc`-Rp zp9}gK3HWXMIY0^FKhI5Mu0OSJTMIulKUw^Ma7iv;?Q=hvXXj$nkNFGCFL**8X3!^L z`qZ@Ym=Cuf-Wk)L+u2+3XyMv7;0MZ|Ek6)>27Zv>@%~yTk--lfq`LCD-_^84>vuBCTNSI&VpKiDU&zMQUGYAYQMhtBqt zpjqn=7;jdv-8o16X*f;5`IgVXt0U!&?$1+wS_irx>Ff|HzpJEezs}B~biN7ni^YVW z-ryIs{;?iP6kq9nA9~N_0zE6jascwQUxvQZ22{=KGo&~9_PwQJURQbOndJrA3E(ND z{)>ERCs>b_?MZ%A{g{Zq!5T#UWF{Q3q{6}9za{uDp>}=yK1b;}CkOR&k;C`#pq*4k zJGnVY<_ASNDQ%DAZ3Dgm_Q`$;(&vCb_gKbL4n{SKKgk@i2f}oX zo>?En*A@j1tA8iKkC=`;S+`M@&-%SWr&4}|^g>_xmKkEl$Mj-6zF!6-vHXnr737B1 z2Yr@jw+jDpp2t{$7pHTT@f6pzPB9Z1a?dE-0iVh?zQ1~0PX_XERHi(1M842@9moSj zHHw@dN@xLWSv|+z^OFlYM>%JW%$Jkn_9NW`Z$vJTqle_E>lTyV1AA5ca##@97eJ%1 zBR|Ib-HfdFPlVqf+O$Txb=udQj}|+6tI&Em8>;5cw+ zM@ZDAsI7d>wD#E^a9SW9>n7CO(~c+NsC_SdsA~N8jy!(EkTQNj0G#e^>+8Be9Js&- zxrwqs+kO(K7TJ&O6l3w>Cp4a|MBkTaJX?QfCe_n(Xi14@v+L980R4vX0e@^4P{*1KEm{LnJzaiKj2a! zI>ClX0?%sXN5AUjM?~aD12{5RK0pu=oxNuHQgo=ShgiZJj`ndbNYYjK{cx`LveIE| z$c=vhjuy~gLC;t?XHh+OrENEe_ty5R#Cr?9zoYGuAS1XwxAh&-%RN1pXJRTpVxe<` zmS19hPbN5=QstMW$}dMNzf6}Okb=+!T5@?SCkjYTc(W}$4xNvJ(3Wxz-8)P4jbo7F z7tnLTz9V{Ooalhs6TXY@SIPTayeB=#zRy8Bly2n|NO`|8(W%oF`UK&zHRu9(HNBxX zaeLn3gjWxqTBaY!ci~q#dJd_|Jh5E%d^--os~5<1e<0WQB1z$2Tj=~zE;^)fwe}JO zx6>68f%QSx>OmRj@+#%GBXJ=~J#VD`Eac0uBY_k`$kXj#tJL;g^FNtJS{_t|oC zE)VPTt1S8*_zlfFtP`C-zxs1{u?XwO9>yl~p!6Nf)%mSL-+Ug=gA86w{9NhVgC3&u z7^3ejdmhi5r~L%+C3+q&=EN;M(t5*+lW}Xj1NaK-RcU%DpM#<_k0YUe2gMuNzaD^5_Y~+Z_{}%mw9*EK;lCWOId#Uoz9o)-`;Z;Z?fLea$vVI zKJ}h(j<+EFFDmQKX|4Ky9PO)pLj1rf1U}Fo^hMU!ouUAK;S;j1F}Xkx^9j|b@Z~DV z-8{Va=)FVhnaAxA-H^Ahz-^A)O^g@!rx!T$QECSrw@>qsMyGb%%XHj2zF>Y}JIx}z zhy6h%X?Q1nBjgVX>$e{EXc&K{cKWRc-n1C~;>Y@}S7z(~P5Ld}2TJ&^^n3Gt3|06* z!ilpU*HZ9n^&S(pdlG@Y#^+baHWYjW_D?oG%jZ`-_y-jqZ2x#q+4@!NpDlWRrRsPy z`C~9%kbvI-_)123-x%|s6uBKqvb+h#uX5%M3;!m^FLLG;8NZVgO{%^ubn&5W$LTNf zCa`-{j{jl1N7iT3Vfp1P!2pRK^JKka{fVE6{8c26tevEC*V$3&!PU_eFDA%SI<<{4-+;3P&oxn^Kp0s%**lYaJodGrd9LSy;fAF2v^xIVX25Y6pAAC(U z{vNF{*VNoTMRTA&raj}Az!YY@$d*V(->wn}vH{AT_9^wPCC??L0w_PYof>FtI8-V|~umh6Uw-whr9+4wv5 zJh-I7?U#Gf^2bhT;&z)l?X!MYT!zIHUOzN{!SCFse-R!UyxHU*8TG3v9Z!F^rp49=~#NAn!lg=e1}%mybfOVy}k+QidlcHPT4pqYbG+Dm^gGzP5CwC z@znh=dvuLFXjA7+y0o3zu!cTN>FskKs40KOM^mTPn16e8+nVwl?sn$dn)0*JwY>lR zo0{`CuG&QYrm`z$+%o^(OCMQq)5bfeU()kL|E+H?c&+UAVYf_O{o~O)I-@>cg-JN} z`y19D(mCV=C$tr=W$Z3~FjNGee&nw{E zNlkHYW`OF6ee|5KZ`dzwRzsa-eOk}C6ouX&tosm$h`*1@v%VSJ+ok*MhgRknQhT9$ zxv1VH^xn7oVor(p5gfS}?NG7%s2tai$aj(;O?$CFsK`-&g(+maQOXfMd_z1^wJEoD3@yp%)sE7?uNqW}-* z_d~xWNT;lws&I_g^wV|YY?nHY_``yI?5M;u`EHW)O~(GTF>_e2r14sNP~sMj)A>lj z5{8eDviaOgAH)6&(?|UmfnNglrtG`&xnKi5c`=PEjF)6^|BCoK1}J{Wt|hzrOCUh- zVcQdYtk(A8GH(2vv5#vG!kZG*rTgfxr-h%`2Tx;Y)Gi5xj`*jSiJNPf&UL)5e7~wF zCiq$6Qv9386=?qZ=gR&U?n7gG8LjZOb}P8uqr?t}rh5YC;}1T6V(PMg%h>y{oI2%P z-Z%WcBpC8JT)wx`@6(7QV?6ssvM=U}ABFaN$baA8?lVTNC4W1953d_==)A7e@dzCptL4j?ehqwW+RCB7)UN?~kSRZ~KFObjk+VE-;);K~ zApefj4E}~f%2vG5`}R~`>08szcD3_MIMAEF^&9-Fd<-;dix3TLtmY+Cgi-y|P(N{iSNk4CPsv(x0IsHkq=2)bOlCVp<`^XHY+k z{IsH*{1a_eq{CXx-xZr`Y$GIdqto4OWxQca%Xw}X{R@SZeHo! zvv3}!m(%sB7djPn$-m>(wdLD?mHhBuhkW{d?%8+0dCJWlc1X6<9%LXc@5>2Iss4>q zrX;5b8+1N}h>Q0vOx(&BU*w?WD+$X_2jPFt$?BiqQPbZD-*DPW+66Bne&n>%{MA8w8Ty>_qm-la z%zy3vaRZfSDV2kEJT`u%A>=mgC#KwfEPrhLQYxx^*Yd$}dM{ISqRJPJW1-kE9WVQH z4RkAl7f^)x-WG~uf&GGgI2vaO^^=OpzDy%CisUi9FQoTnm@hn?Mkbl@zFhqHkR!}D zgYvDNxXR~_BJUx0NPaXC`GmL#o;MnpZli=~%b{)g} z{Vvi12jn=FPnd+ydT(xXp3gE@;e&LL_T@f0!6%S`J*$=&6YWp7#J?5~XWRMqL_aeA zwNgLQMxT0rs2M1Fj`olHvKjs}cpgz10XN*PBj3?Q`&8a(>gal7`vgRZxFRa&BmSrt zo|q2a-!0Jffp{R+Bkg`+vvhmW--mFYu0MDWTli&ta)Vg66xUdau^X~aVK-u(%etlS z?v+r#a*i1BU#joAhpQeGoExqYu5A=9d+((S6kuKu-+}f4C)OduN%{}jt(H$kz}hJ7 zV=CzB0VQ}69W%Tj1kvM!PI3D&&$|=1aKnGVe9Oim0)I44(i*yAZV9u5Ut{(K{%O8F zN;{3z9|3aZc?sh?VeL?{YvBdp^@X=OA0fS@Ff@LO1EukA9~!@oI~bqt*8(oqw_Wfb z9(M%e10I|fcAYwWFO?GqzIeX$oI2rw`U%;)P8+;IIll+!PV8Ob-=FKcF>)`tQzYxt z*+JHsb85i9dEGhKry+R2zhxcP({wv6zuLA3yo-I${Dj8082_0v(~s6Ak%L3+gy@YR zXS!;nbf^9y(!;nujSTxePQopaI1!xN{|D002?;LZPhvM@sorZ{K zg&u%7YsG(5=P{=9SS3za?jx_1|19r<^G!zmhFkbvMvSkR<)iWi8vkOV&!q4ltR1yC z_1r|YctLr@{W&xL0Di1oFtxp!5Y;Q8c?tZcxpMxek(rjEj}_T-%z94E>My8U zNcG#e3F6oIu^)!oy@cD1=zCZ6O!eypdfjDKuZqT*-zU^Q?-@;|r?GdP%+Qxol8;d) zi4U8UkvIY47coBr|5td@^&?~Ap6Jg;&Wk{g(>OiE>w*88GH@D_JJWUk%fNqmTru_I zRMD@SGXJ%`KVy&pa) z^9?#GU^+_Z{I_(iKEisGX$#iObl^57JzV2Qp#(v{LHqb|+!E|F)&E8QLw>Ox?sM#`={b_`?5*XW*7QD_{z%h3B<=W`?kVYj z?*c8=={1$}2t8ZuX0fMK-?8V(0yxXjF5yM-=XBS80{yJE{ir@zCh%^uAAA0?i-{9o zaJs1enh^cf*~5(+tYPG^e=X1#i)20mzK7{kJcyo(xmb+2?M134IyUCH@b!D+rEK zf`i&4JLqJEI{`T8{SdYjpeJ~ij^(=vz4d*E(0lNAp2r>W9`i}(9f>Z@pT!q=^DKrF z<7ff2TDf0_bQ;|NpOjaf9VzPzdf;Em$6y~UCOE_R>l(t7#b3A3dyBvJf@ld(2{*zF;DQ3C}@RnvbsoywgpYQi_fk&FR%5@yFfB#$jIHhz6(3oDYviKH$ z>9p7N1=&aR;_M0fC;=9Y@`Dnk7t7D~lKg<4;!Zq+o>l^ts$^-Y`I{uJ6aF<#*JzmFvvp7TVNjlaqWaSm7o$BQk zh{JfMCH}=d(`j-G$P+$q6+bo zlwDWizr!2mkKRRE4owjKd7o%HA#{nja!YR_@3XAaAU=WgSoo;$T{+>K(14lVdIxFl z*WN$kqQ6qYBM>Y0c4T*XM#t(ibYHli;PDU#CjPz(;8Fe7KGS*Nf2{oOq0oD8m+RGf zUX;%OEg~nCKSMvyTpxPwz(INOd!h&6+eyH0LGz(_1ZF$Rc+_u5B8}$7o|A2CGU?fY zMK}%P!#d=7Oz1s<#w%fZO+*snABiSdKR3SI1LVYvHw?Yiw|LEoi9aJ3^o0IQmGc{Y zc;52^I*{{8&)fQuBSlXS{1_VF9rk0?`nzWAM|IJ@gLwnClB}l`M8)n;cu7bCdSC7O z0Y*AGGdZ8_J77J{UqIsv&duOF?;l@3-ay62rq<8Z0p3FEhxsWcS@>a~#~LHwXYd`N z<2cqA(E->kuM-#RmGl||KWAswi-b=VX?bYJvYuS2?@)P+Gmefh=P;wBerrpIN|(Jg z9p*pw{74mii=0h6kHq}R%9Sm*4j>wM9Yo~3U)6Px>Hpu{bpReI{H2!bU}P|#ybe6@ zGO7R9tOIJF)&YJ@KlVJ2n-Vxe`v*i#f33c!{d%{B;Ie-Es3-I_J1cy}f2BT(dz~`+ z5BRIqKRW}y5<;iOsaXB9CEm+A+cNs+?jon{dO&{?pHO8TZKmAk_4p_F*4IkEo8Vi1 z^MKAnd}}l1zMYTS%6&h^^Y>@vzW0I1eQHPL2+L1crz%HW_EUrFWy*=9@--v+mYu)< zEBwsQv;3%b{LF_0S}goU_}Ow>|1aWaXg{5wW#wbKFA09eahs^c`$FD?pg!+E`0&q{ zGW>ylD1J;+yYG?F8Kx4h@92Im)+zR7dA)MnkgQw3Bjj%(y$$4XwQ)tzy=rwX3Nf06 z{tmTh53o`Ek@dq^dL#g=|7pxi^_hU5;#jg}())0JHE}K*v(>j{Q`rjW`#&3pv`5Z$ zv%1f^wJ*i_}6VW zO#k4q2jV3^9Z`7b>cW4pe)+i~qFzP8$*_B%6va_NYXLx)sbhF+Eo88T+* znP+y3k38d?_9KU$J#=Jz$k35Phn0>U8XsIba%9eubIY<;M#e)O+YUcy&=noeytmP# zKi}@0Uesgo!=HWKVBDK`G@Ford@0?j6#gaBUtBcg&~x|g(5$@Gf!96s!;hE0*71w= z?S}5N@4c6`f979%EjyR|`Qau|ADW~7vfjMK-CDIGl47Rr2PC-%&8#9vk^9N|ZWExM`mKDJ!ivncIvbH>4;Io#*_{jT6)sR zq5a03G49-MI$wiJPd^=CoD2k>R66)nLgFaOkB==KGi+!UB--|?dCtMRlzksYTqs>( z%Vm4sJhDgsvs)i>)5_xt%I@pjI(pCU$36V$H@mOD_1Uo#Z(QWu#|;={xk=EPnFZMFKF)>u7Lhb{VX%=4>0?~ zP=?HO-q7?>nto5yGc^5yrdNsn;H;7~^~3!Z0V2fV1^xPNvFvN`ec8rYV-~Pq0_CYc z?Bgy_`(a=5mr%V+=)G^>YdpHCBm2Z_OnFBAqPkK)V($@+ysG}7{cL;OPftHi{Wu?L zoX$spli~tzQ8-f3gyJ&^U4dw(oSy~&lZb&aTGEbN3Jgm?(;G*$4 zuW5R%&dckP4*JQ`e=6S*xP6cq!w>&T6M$RKPw+XJh@883zQOoP(9b#ekNSI4`+HLR ze@D~nwf}b|oz_46MzI!=nHXPvJb$7mV)oFKX^DRmY#$NcI9+SF))HK?7SW%%y{Ohl zl)biF6dG6E%yHR%oQjNgP~CHwj`ZBW^&imncxETSU(Wp@`VsZmujCZccbu_qd7hl5 zq5VOuXQtbw>Q@WdOTj!puXubx@%oashi*#=?=RO1uLf~@GM@?d>l0o*#U0DU*YS)3C2eTgU<4 z1jK~LR}`*870y?;Fq~fyJ)s}FUf$C7PuKq5-opJcUefw|LEArH`+G6eUzkq=k=61i z-%iVhm{Qr`PSL{|~%9||hV}Ct;9LD)n*CXUj z;Xb%L+K!9v$ss-%9#?*7TEu?k}P7 z4=Gcja=9MM<)>Kw5`T*je{KbbiYpi@x z^(l&zE(O0p%W}>+r;hOZxLYo8S-zuwQDe4aKS)CJQ!Ll00onIV>iY=3i!OKx{3kRX zcTS*ZJfrd?6K<6o7*Dy32NzA#c+sDtyM`_+E4oc{f^e@P_ z({vuOkL1Yw{W-q#Ev=_O&p>|?C*Z6Wdb9HHzmIRv>c72seN4-Yhlygnma zlk=@ucg3M~R~%Y*8Tzmb9e?pYtmpE&t2Lj?l;2kWgZC8-l;$zack?p&uI(qQzLxTZ z4n%JY^vU49uW>>T&c`N^G2SE;7S(^5PbDJBjPb`cZOq5&=Zx~RERv9VW+(6u|UJ zRQT~HF+InUGfdAt52OCcydgZHoISUFxV#tmolhdnmz+w0fW?k`izidF zIXCJ1o1|RMoepe7{3gjdiz%LAdGY=N&h-HXP&S{_aN8?9VEYu`YWEr3UicH^f}CW1 z!dWWxi0BWFi^lsT)7R3d%1vne4z(Z?v1Llf?Szic)r6=U`oPd$Mj^$Tgm? z)|MYdo}urXn4XqD#3Y@FXk2iN@6|;KzQ3K0>v8+I05E}mpttcj1AWUeDUOf1Me&-@ z`@Bu=P@#jIXC0y=6PwKRbE2L9JUth3m(oq{qe?gDGTj_&=?3xkkV)=HTQBDlp&J_q zldE)-J3{CocSI)LMD?EDoZ<35I$X*{Z&11!#&lxwmJ+@TfFC9}A`Mg?#y~HG*D|z6 z_{R6tguV>NQAL_5d`|uX&HsYxJ1;5URs7;SgwWaFBu_ld%O$j4bsak@Ph7;C&BA*t zPaxki<#{0AK!RBJh)cA3b%OQjxTcNyNbwNiXBo5w!B6;{W*fqXf3$^1=ne4jdUG+y z1dsEBQ>l;lM873SQ+qb9@<+)J^b%C@J0PFd55buN{iD1)U+@e*&-3N1k1?NzI2p?2 z@Q1)15qfw0gu>mJ=dHKGX`-xG<37uz$W8Yorqj5c=V-q0t+S*4?gU3f>0a009Rb}6 zd?xoO=|69hKJ%o$nal9Rb5aV2+<*P3(2t9D9GXWvPKasLC;mWu8_Fj9jxE6^?RVEw z61R#p+ha<{5L#%UCH{@L3E@w^>VLlad*$313J5t64w3g*KbRkq|73qa9!(Q@8H<+FyirA3FIHZbxpBb*$1$QZ#z=4* z_jSfw!qbt7{Xpj?PlRqn0nO_3lzO+e*9BUX=qA3^j8Ij^}?>^Xc! zQu=XWACR1*i)ZL*<$%>&!uS9lN_ifI?>p;dza+SKU_IhvpPLJMm#>z5Sj{WEj)osg{oH_W&m6$bCk!8&$tx zy@qrwZm%`Q57|TW$>T85QlUTlT^HQXL2=Y3wx`flY|l*k^YnbJz30f&dyl*`mHy;@ ztSI;?;WO@8JFOS^>(fLjQpRlU}eGt1mY5a}K7dd6Xrd>BV^~FBp{RZS# z%Co0wy}Qp^J;drafnW7o<_Fz${JkCFD?fv-z^Cz(c-SI&Z?>`YChwgu9b2v`!|!Xk zc&JU95qI|N&5ft_6LS-Ay!IL+R*>)FZ?PvwfQ zawfuZrv~|i`vgdS5Q@Raz^P>%fS>W*+DnN1^S&3m7W`P`p`uv)6CYX-K3%=sp}IUztsy6gZtOS$xg-1Mpi?V%4p6ysK&IUB^&EC| ze|hikFXjBPDtGo{I*D1i6WD9J^ZT&evGL^bTFM>7N;3ZK_iID)#+#ifZ=4YjNyMit zJrFCe{607FdE)Q1jKch#&+DuGLj2uBIS4+9D+UI|ezAU&DsmD{(|WLW%;5i%d>(j< zDxLq={h0r!`Mkft^6A>~dH-&^UX{;3Ve9{8c1q2B{<&K6`3fmV*9g$M``h`vuY5kD zd>-F7hyJ_Se4gMhCBE^$!smao^S=o`4}D@W!!3M17{`Bu&qL1O$M!|wr?hsZW96k; zLUPeR!SZe1hbDf@`)qXI2-8v6KNtQUbl5+a(EV(CF6b+efY=p${s;FO7qA>|hW9MD z^<4Uwt}R#Nc~{VNf%x6wh^BTEx=$anE7I%~IhP!V-9r7TU(qzvbdm1UeuXr(*IUy+ zN!r>ef4aS|Gus0odhd#%`_draJhflEPOSIEgLz^-C~~UOVFL1v@dgv31?o|N8J>~8 zGGDlFTJO;_dE9Pm`TnD`K6jevRrn$k)pOuS;dTt)$3*R4LiB5L+lao7dSsVX`X_JV z_TrArpUGAD=jMtZ6tO>`C%TT0+jVmF9_id`MSsHgFWIg(M{_%geAV0L=4t&I{5~0s zo7;;7`cl4*w@!B%ciqSISy$VAU*NNTS~G*-i3}F{rgn&)@vkTKU@^BxGH&V6?wkPiQ_avHjI{K5|kzAAHXlwX5^di030==L346 z%-eM`UN^xk-R$`ov03wRuH}b+a6Ycr`9M3>&4=@=tnY}%jhbFaUkQ97J&Weg zbq(*A`Ci-`Mf2&){oS;(nf`P>G0o+IC%$iq>My4HMYdi%u8Kz*Rp@sY z8DA~=$yZ9hXvZOXq77xt8~p)(0;Y@~L>caLWV(pHYw>^&&~iBhlJT>S#1BX0`^xxE zE6=y{p&J+fXZg$4aSZ!6DZQA|2@~i2J5%o-!5cnE zfO(I;sdz}j|AuxLXzC{+eiH1{1@Lpb3D}8LE&=3D0tF7mHirH{JvKUha{me8k&08s|dwc^*u3cahH##eYQav6J`` z+_xF_YubIHP3_wa75V~5Jf52O?FgR5Jdcqs0=K7d=)G}klzytfEq)2$OZOFHy04gX zr0!#B96}~ufu|v1_-Gy~@g0elGhQ(vMBkP0l@PxU_6-uMpBlfE;Es;9^Ad{)Uq@gV zw^QZ*QT`rd6f%d$-5TE|hMpb0#MX}t<@#P!<3M6t{sdeCcV#v{6LsjKp^A6I7*5BT06WBob}jO zAirTxr-1kp_N63UIo~BRuZH*g(XsG7`@L)RTc`0+j$caSqr&%0jP*okeMG-Y?H|eQ z;yz{KH_<07Jy?H36~7?s+tEiY9m#$a%?ppmL=vR@|Ew; z=5@Bc<>%Ny9qNc(f2^w)S?{f&^db>|7_`jTdwvWI&+B(beHi< z0Hll;*%F7gah}hXNAK@kaO9yi8P*XnoKdh$w$3MK{t(x*L zy=M@3MfM-^YaIW?H=2Hyy|yvH-wQS6kJ|l*SogAwVL{Iyh~N{KI4@=&wR1{ zqxxq>My0fb7QZ;{RmPv1-!ql*w&p~2AY`kdWfu_*ytrifn>(Ib@4TGy{SI!kXs7qv z|8(#fd&mEE?H>Cd`^xS2=H1=oiuT7%zxkR8V|Q=y=FXk|Gv>S@xkr5T(p4Wd@s^Gs zcg`I*{(jo>UWMQHd}mX_~ww(2^@##6~_&vLHJIhfAq*Po4Z| z)83EY^y0PGUva!sUlQ@#!n&_vJoXa(2#&aRUz?b5peLu#3umKX&|vU02{9fL=wHuRi0W zvA=v(wzHY=(99S64jpsX3j^j&ZZ*8lK8I}m9ueIqOm>Rd`aL4KcXjLch}a%;FH^fI z1-pmz(}en?%+~J_h4p%}&$fP#i1*iE?8AT1568~ZEaoqYn zBHbrSMU$!@C3(LtW%mJnxyN(s_lUNBk7ys-1c6(X^rEfbBVv63YenwA09w-TAtHNp zG5)LkjtTtdq&MjIJx!UOQ;Ne$Kz51V|6$5CozS?lt=}Wc!M7+Ne&|g_5 z-GH79@$t~T^p)`gfA#(Jr>fnZ(Ealv8n3eTdqf4iZ(i%~@_M?DD*LO%SJ-Y(*|-G5 z_rkkZS$t#PkND2k?-6l)!`AN+AwF#D_lOL%*@W&-7qOpfmBt}#{T`8=d)xXwB0g^q z4=Cbn|HOAa1}Yy;TD=bZoAf)xZD`y%-v{Get>utG^jP^ludUxBB7AN7yIe3u2(LCC zYwP!jSWj@F_p#m46!cd@bSH5_h$oV`B9I%$1%>@0R)<;;QPpE+XN7O;_lW2m{2%=eVHER4-xu4<&e!J2 zeedhq@vW`jBXa&veqYT;*eu%(X6yHeBrXy6ILZ04p_%7O44?m@Sj7_NV@{hOj7JfH$_-Etq*z@3$3b$YGNy{HQrHR{Z>a@@LU2z$B z9R8p2D@spo{T|WQ?-8ZbL-_Bl-y;fRMA915mMu=R%+}iaJtB*V;2FnXWj6JH{d+{m zynVRy*xG(Ox4!M-3D19-ob&uM_cplV$qqlgQ2gYAYnLA1$}y29YyQ>#n2H0R-~NX+ z6T4rvd-?Fxao;@D=JT6xX!_#AE6bqW^G~?6YZ___#z@Bfa>k#HbBDL!Z3^d~$&O2y z6z@B#oncywKI+eQ<$Rsq&)eXiRxabayEyM=Dsb)v@!S3!oR@(9Y}(3s3I8@p)4lGq zAjkdNHN8&LcSzc6Ci|cMJV{5e21rlx?~*k2w^;OmjQyS9{ZO&NJ<_&4LbJpQeCQ-aIDTF}a6Kt$cao-`lC<}{!ns7#(EAzgrzIWq zTV(kDCrX#_IN%=^_}C8-l=JaLPT&z>6JDV$!hUNBc%G5{b1GLz^*aOJu38TJS^hJw zoSP!MvH`uvoWq}ibJ(J9=Al0J0=!QZkMunZK2Ie3TL#~_;`ZX2rgm^{1@yuDKK^Xo zw^#ere?#q4*h!{q!%G`ph_W~%ifVB>Uq^5f9&{XZ-2tEPg5tn&JILU1o8uLp+)d=? zIa(ih!FZ`*8ei^Hc6@)+cxKRese50>5yv??%$GkK=gR0D8{Mx!>%lm=3}>A0aYVk} z-)Qf(s;hnz-1`(qd;V`Dm`6O9LQ!(QXwBtVmdLa1U@)GhM=lkjXvobFH)!3Oy?)}q;bDv z=i6rQNqrjTgY)=Kil($avuw>Y2)=; z&`i&-Cq1d3FvWxqtYaHlbJfLS{SVK|Acf^_OlY zf3WlQ&%c-t8NqL)zMVIt^WqB~licSPp0{LZ z-t>Fh{(F`4#e6;We{8;{|Ci?L@0MR!0K$K)oWceL@gFyo_Cjetl#Ybb(NH=TN(XXN z`wQfxrt?DO^F!%6p>*9)x?U(KOyNrALxrHe^HiUb`>g%hQL9hMJsCduJnK_1WSFk~v7#^G9$fJA zjl|KX(PdD$6Q(6D4F?^UGTnJ+N;%wXbv+(?H>Qk!M5M#qQ zX?^@X`jqN%o2^e-c>f}O%6~T`pSS8$nRZ<^eahZ9yH%e8zo~_NZ1vx*`c$UAu~nbS z)Tic)e&jx<`qTrGj+~_W(}S9Zj3T+^KP2fupCbLNI=`6eJJx^ZFOquR7urs_rZ3a< z;>~LpYF37yAgdlVL+uIWEAX4JeNBE!@+(HfPhtEo)qV@^MGe{6DY17+y{plWWxo?z zSM4*pj|qB#zaDx*3EWl(sUG`p?H`%}Yq{!uopWz;0%LmH&*;LmK_GA^p#-5P0A*jc52dac`{$y$dDCE(r7)|8w{$ZxXv= zvFOWe|NCo!PrT2)L-pw18TJA-Bz{NDkKh_0q7#!6e^bPjbi?tmhl(*@cME=^Sg%Yc z_bC2u(e%9`{B3DJG1*J-iJLlZoc(4I>!$&|u^#KN9vjQo@!InFq4FC|M!86R)FZ!_ z1G@+|P^3WYBixX{X__C_m-9eRJE0uOH}?C1z%XCAtF^y`___SoP#$>9eOlMsor(4zY!_W6e*Wq)Se8>DT zdZ@ZzVD*czKQv+27sk~BddgtXEcx-le=&c-{sT=k=mnFBfwiQ6ojB<=4esRfw65g6 z^~+!%BP}0XGn60G{8zNUJitM8QWtdB6}FcxZyemaiGFOk;QKuI{u0|$%Ks7^uSxLw z3}4(!$UK=g>NgFQ&&-EEw4cm7`&lc;nRKO{T;OLX;0No3@uTB(jQT_PyR_BhmuY@X z>lbC#cWr*O`ur;XSh6Dvu-@unoh_~0uOf6+E7t>gD)Kk-Pm~0|b9$RC=~rpUltTB7 zW#hNA(O>1dYrveiJ#4GqsI!l$Z|3k5Un$Js*F-ns$H}Z;k1h1}rmlnK!r#@YA}6d| z4CE?*pCVYtL@B4@5>v?c#lVOE8t`9&cUkzjd3uZD3;TC=-HCrDn_Tl2)=I7caNr4i zHK(tYTN!w)nm?rG{D{8^eEdPa1VCh8M(v!)5uM@#z3>Bn19eO?~mGa zgw}IS^zg^VzZvj~Jf&$>z3_k1-$=Rzt)FcE#vuQH-rxA&-M_6nOzF^6zkeI#*X(B` zIlOiM7IHDQsknsy(f!-4{zlc0vc-`=FD1KW>;A2^KfK?*)!(?)-^liC_4~Jf zP%ks4s+{Ub`YGAnmyo_-Oq}CU-hv%CosuE{pz7iNGSS0qTpr3*Z|7+IUu8Sh$HUP* z&Wrbfoz_SB4B8_<#U|Uo5#<<93};~P`X7h(huBWUaInu$b{g(ACwtcTpU8dai1%PW zCdKCz5*|`cF}Isgy*L*J4cWmdlOX)y$KOZEg{{JR7W6^(tE2>fCLY)k5)YJ;??9lQ ze77l~_9p_92>;RlsGgBRKRh19p<(=JP3&BJZe7k}OycjPBOYy(*u}9ia?eywj^xkH z(eMA<$o92~$#;ShxjbI9c&^ypv6S=^i*dbVj?Et@`O&20>p75AO!Ft|ciPP`X+O&S z`W4Y6!&7FP6_)_Bl|&(2cBu%4%1y0`q|w5{=lx*xS+Tf7k`B8TU&p1 zPB)pi2+>6euww0>21-wKe>mnjLjDBgTfbpeI?tp-q7$xPWu5#n`=k=Sa2!^`(iz~) z@Ds_p#m0F7_RaCraltQIPV|)xK9sACzXD#`0>P`=L)qHvZMLL81AdGQzi;Jw|NkUT zB!aubnV)2d6AAMFSL`;UaUenbt;CNaonXtZsT=> z_*{ISoc+Ew9>hqzNGPs^8l-+8Mkr3i)PL66ZCT<&B%kPq<4LNKi_DkpzSjSlIFYS! zx5X^qZ5-#;IFXv;|F_z05^oJXMd)U$-B!!En5}WQv~Rk(akno)4=btEzo17M){n=l zoiRuC&WM(KTGO--g?gK*hgRxGf0sSVF8c1|z6B|V?)U_UKg)48(eWnVMJ_nyE`zYtZ^&XwgHZ}lzM9o?yYbO1dsce&C7 z@5AElMzjMOK`wuX6vwi}u8xR3K^cbZYuG7CJab>Hq$lw&Iue(o@eRBq4IA96tNlA- z58gT@3!SHn9Z<7gALRe9==Fg<|G#yfuCATuZ2K`bzp9@r zU7shuxOG2f>wXOQwj=sq^8c~-C2&@ib^p&P0BR!f&KViEOa(VYGtL!o%Vh)= z&80gqBcKQ);FcB_MNQ4L)ZBGY(oA!$G+P)_v)8qJQ;P&9m)zIN)HMFz-}0Qf=iFsR z1YiIEzt@kPdDinhzy0@nmLvN&AT>TaPX|BRe0HAB>M_w9pRLDywjRTA`O51t{$F)p z6UDK}9(>i~^V0q^HV)>tIM;x$u)M(gEdzXqvJ)wdn?ml?wnM7zhyDlb<2>xKlj8Om zt>?69Ts^jqvZ%`bB79?!c_jDQVutel z&QFOQmIa)6o>k(jQTeCN4%2h_fy;D*_^R$tPpO=8`Cb9|le*qv_bKW3c?SSbM8DYY z7~pB|J?QO6?h(LtcBzN`_EZm+wcLM7-}{#MD(yEW`zd8c;NF4jBRV^P{WKWvT94Nq zqVi~cM9s*}6@C=>AvvCbUG`oBSMMLN`|^a}YTRpJ`OV&Iz~iC`kzcX~_Zp05 zzP0{}%#-{r*$}Udo9K5lIv-G$>ERc51P;R#T4`4lC_1e&+M?z z>@e0#RqU|WO?)2*#69b!g5K8>+Wi~2CuB#^WtigoT(G~dmG;3$`#%clR@RrHDRA5< z)HJp%aJ(=`q@fRGKLqZ>*@61ya}UOU(S)&n!<57qL;HObP4~99u^{d1_v-(@lZ>xYZnTUUNv2stfsMdL0K@38wU2QOCqEB?#`Px%t@6T(lsuW9U8qx7P9ybbr& z1&W{b+0F|co6d7Oh|-KNQ;6^<`uQ)yH<2A%^rU?l-80xod=;&Gs9t8dLEo~tR@z7R z5EkNocE9zM)vtY>_Yr;#_dott@5!pe^H$2&`bt08Z_e_q8lJ$Hf;W3#58cm6eg(cmB=q6wzHj4kJ{`Or zP4dDk(tQH{hhpbP=jnm}*nMr_d-fAvO!xz#`WYcS7+NpK{iY&^{DSBgpleOZ;(p;3u(w!#%6?PRm21`UUcm4QHJyW@ zxCPJuMj3bJc-fy~GMt`}v3TkvevI%`Fr9HcWdu&kk&k*2y%e&Y`rabHcS2U-j&lGE z_v~5xzV@8v=W>dVOZoka67|EJ^k>Z3Twch^yo}NF06LBr6XOUI<5-gD*XTIzOY1nW zKL_K3Jy3|#kD0;mEk=Iz37ihocK(I<{4)ief2Y7*(}-|$yC9LaetVd;bXSb^7XlxI z*U(6Rfu?PL(D*VQg~J;OICLreLL+ogEQ604x;>Tf&8r8?RLfNNfpowe(X ztWWs=QTc83{vz_{n4h7?>ANU)o$r~b-nI^{@41P3p{@u1DNzq*t@w%79#g%sLhE@z z0lyD&hTezmYkICHMRq;>%TddgQhv&Qr-k!jHA;S%3Yne_(;%j1~j@;Kg=^%_jruCt63eTa1^s^{5$KqsUpVaH0p ziFwjFfKPIs(LJv+j?^H9cfs!av+zDp;Qg-8bc+d%kHO36JkyDJM)$-f=9&70=V|dt z{vhEaYsc9k_%mj*;L8*G$)yxO;CI4T=mvr>ZC_{GD?W>^(wCOk+w$FP|CSzvev3*k z4Yr=%o7EH1SDcQb`*0I*jO-zcr%#A4EF1^g@mGQ4pg!Q(XxmqT<6v8!fMax@uN#M_ zl~+BHe2L#{&iC5@pO65oC)br<6v@xTdY1$F%5qBO9p9%@GM;sA=hq$&?#GvS=n$Mw zTEKcs(!c|y%|PW_ino!R8U$B`o{g}>=^5JiUUoF=Ip_(pm$AKYp0__g<91h|_L9k- zF6C;kg*nTAVt<91PT{|d_JebpWY-n4ww~B!CNoX?(eh%VJc!P5YQ4(#7uMZHUw3D0 z|9h|=@9_(At}|`(cV~OR_#@@L;6t+K68?bylJ?h=Xct6sMcYGupgr`K?Vs(6vbb$% z_viI9PD=ZXA27zoNm(z$zQbxyJYMRgE)Y1UW&ebc?>jX7UgA{n60|%c-#u6%=WhL# zS%HhmoW*)!HSF07fFCE*NqTuTFhyy{NE#*cS+1sRe_8G?H7HYL__|qr59opR->uFT zd7rfH-?sN`d%&IQr8C;j>_Pqeuc|$$-@%CRY8?BWhhbDNHAwfxJ9uKe_;v93GD&!3 z-Ud+ylGBRhn)l`N!q$NvxzW#wFB5Aek26TO|(KH%~Bf3y!# zB|FHu0&<+}mSF})Cw_>(40uH4dmZx04t3Oj&6&{5f_eIw;yGI1)Oa^pKhWC)yO>{S zHJk6{^q+F{`%qHPem4r`;y0|Vb5lixqA1RWZKz})mF!ct`&fCOCe|NBUuF&88(g+8 zHI#LjvN*c&hw^@(q9gumsN+ZYnC+!T*>@e?M-XZ~V-8@uR`1g#OHsy|lKV1@oG*l3 z$M@}07);7NE!Q|%SS_xe9>w)TM8d}z%s(A+TE7=&-c1)MJ_Cbp8v7Bh;u8iakJ}ir zy9Fa3cCl0q!)vE~ioEZFql6)Sgx^dg&7FU|4Q}Ug?}5#y#Lc zrS~|0V0`ckIuV<#!Y2c_)pphR$$K#@zTZgV+Y>(NauR%! zV*4FZr&ap*CB7VLJ;)E`lUDY3NG>ry`^pFYZbZM}6D?2jk=-BH&wY1}@{Q#alkO1y z$Ou0f_`T!@*|@fss2^rpmEPpOqC#5cYxLZ2sN*Qx-=^*CdVrsNpRVVmJROJnDMg9* zVmxCQPDRhYZ?gE!@CP`KYM5UK;+vLj#FzMtQ!+osugjD8Q+_oXzbhp8HjMaI*586C zE&BoNy1^&R#{;(!ek|MHvMuyN2Z9*-kOI!21JaMW4#9pG zt$XOcfRFfHxWLA%^}eox%6q$y>$C5}#m;|M)8l3LLE3#0Eq4D8;=73dmepY3|CO}8+A{lAGf2vJMNYR|0mxT)BI(Y_Ygpe&E*UJBCZ)-GJ< zOS0V&B$q z)Ux)IEgzI9&nDV8Cdzevzzk00H`sZJ-s+8S*?Rh&*Pdv-a0iWV@wlS%j#pT@sQvV= zhuU?B&3GQVj|BJ77w~5Ml+UC2>w3rpPs`CC@jdxRLT>_(_V_*&lLc!?`9yxtqO$!b z2XOupNBad`qhIVlh@ERRIj9oqry*Ssd}sJOzJ8C!I}ukWdclAs-b?ld@ZSDX}h1xmdk!FV>YFBuxELlAK%H-@4-3+@eleL|9%JKdcc&$ zzx{s!o_R1Yrt6U1;aEBl`bG}luQup*ji+RFUxA-03S1;k?yrz|gugPQ{F6RG={{t; z5OHOtdrni_g3{DK<0EDJ%P{{S9^J|O<7V_dYS*m>{LiOm+P`h@+4jPJFb}mI>^j0r z6bBbM5c!jO9}7C%kn26`$oQ=sX>>z^v%ga9YUAjA0A?tU+v*{;gFp5UmC)X1;0TM{r~-)>oU7o z^kdoguqIf$`2Xwu-e8J^J-p8MH6-letJNO%LH}ZZe9HD$=y((Mu>EeI$+$Xhs~>%} zkHrpdyhGz+8MTA`xc>329qd?r^D*tI zJA_I5^xCq0GHjpXyJ@@6?^rStn7h4A^iyv8k@U!n1!|p1- zhp{{k_esTz5^)-kHOqld`dl95ipU>Wl@t$xUCe1Wk^b!a#cQ;`$bUMaPaww0?~Kl& zTRrkYWj*4*63_QvL3`*M{Pf&_si+6b&bu3zzJnR*t<+${=h_B8q;^X&?ss5)K=!dU z`lDiTzc3z8#=6xgj$b6#wSss(#6`6p>=2?e?ECB%dXqR#7+(jbaj~2ap*N{ssOzha zulhyTd1=2n`$KY`3SlxW7ds6tCC*)l=$X&`(Fpvl*1$hY!f}YeaR@xfg`}6dJ|%Em zZbs31HH}O3Wgl?V^JrKGSNLh1$eu^@5^xU_=fnz;pRDKA;`oflk;JE;=+}+|-}e=H zq>U`vUu_-tzZ(-kYzoU5^{!40C7oz&>vHe?gEIi~q4e1Ly*Qa!h@c94IJmT}k zZVer0ho^9r^;F}_xrw5sFMGbw6aH3snJ(L2;Z#h(soT^3^LD=CAM`jDK8z>7$HpTQ zaO!|OWx1p0B<#6UJ=f#kpBBI7fBF85AqBh6EpiHIj_VD=JJyD)#Z!?V*9!&9KXyND zKm8=TZ??+0SH<2F z9_dr=>ZAO#8b8#>Fk-)uAs)-BoS5$p#@@2(zssiX^V903#{6OENSs5i{(Eudkd@U- zjY0XUTIFwl`0Tm0%JF`{vHI`XhOxK+I-_1_`KFW<6ysWHd3Zg_pIa*XelTIIXVy6dP~<$L_(hMN6vk=trOt@bD% zSgU-SE6=ale%GcA{#dJh_UY{kwaPcV{HusLRR4Yct5?O~Px7SJuToD*;ghVh(;trc z_RSmr`I-lowfy{%0nhx;`%Sm}BUt*vcmH(n)TcV0smNXd`Y76dHcbC$!|(s%?T#5Q zF1lgNSMoh>Yi{a;_Z+>xyYqP$yx4l~>3Dx~2rafPJ=;$XX3m~De`edPnP2Z{FD;S3 z_p*g;Q)hMT988-td;Z+EY4d}b^MbZnb359y_nzEU-l}HKqSDfHdoou}-r&&nhE8~- z>GwzS2Bqy6Yp?Gd(I`oPOeZ!E3fkH7uWIu+g@mX<&G(^H2Wbz-(~%>!HRbjT(x zla6or{ZF?DPFVKV4euVg-!Zpk`zS|9x%@{oW^*gi8h>dMo%`g;A$S#Qnz&Ub#d*G}I*tobW{{OhnI z-+l7M6`R`rl26;)0{chhXHT-7O>6aWHl2KG`O>Nrv<)i1H_APD-y2rhs+FEm#R`8= z{ot!D7hbUcQ>)KBMOB(0^BHjUS$0 z|DXF_oBHdmZ(Ec<^|>Yc%{}{${crj2w{AIT+@lBGcl^DNAD*4iw$I&%gLE_~^mGU4QyY$1~erKkv^MpV<1h?YB?eJM5Lqmb^Os{lSktIdkUdZ~yYR zA8j@9{mIwuwZT_9=Vji$eb1)%PC6<5$iw@dJLLIoum9n`zrXGu_qCq7VfKY%a(m$( zwd%i(E-XbtlHWyJukl+wzqNl?dfxxgyPcKtZJyF+CDN=$mhEU; zwCx}JP5!9AeC1~+zBuK<2YAByY zryN%)zeZk5jk2gnaoF{Z-~D{d^(V&&3E{uHW}+x&AL{CC~&j@+qfzel!O zeE8Q!44$-kxGI&|=)^(iO=`d7{fl-1!~c8fkz-Cf=D1%!_3HP2FzP?AZJ&SU?Lj?f zynJitl^gwTZox`8pL=voN}q<-u_(T35U+Etq9?DnA_!$hdk3*q=HN-rXuxq+tQ9V! zIHPe!^Lg?jct_%+vM%^6(s?S^xY=LvN=g?se!3!xi%7f+zLWHacpbj$WPHQlpEZ*; zpYCxX|1>~6@!Rx7e;hyYy^#EN_=XORr^%H#&R?|uq8r4<)r9Xl(7N=~sZn%~ktw>Z zT#mf}ZPdP))_yaNoRdE(<9`YHCVi&F-^`|T-oga-4$pm723To!9)l)N>Bdb#sodn^5NsA0p4=l>3m(3P6}I zju)U^G1bZE|FHJo#`D*6ZTO&>;QR8N;5++6#rGi$uW1p!)101f@trwN@NLh5W#t|n zlbs=W%+3(J$~_OXpHlFhnJ)S1=~6F!rsBH;@l*g6fNaA5085PTp49g?xgpbS0d!9E zT*PxJo*_769G(^)5xpo~tUyUAUAP$|`yjwi#9!|MUc?toS%DjPjp2li)dHV7jgwuW zaH_jU;dC^^n~jv0Uwe} zAD2GwvE$9^c+bwtIO{IgaekHIi5}FkL>Q@BNr`Nrbw{#3@}Ytg(FUik0^^o|8UU<-oy0?^ZvgT2((Bp!o+#J*Q3iK-OgnDkt^4nOW}Y{_}Xe zJ%VQ+eDBoT@%E4J>3U10L*{#DL)-on;`{UT{U4L>ogkVA>viXLI}fK>@V0jKI`uE~ zr|XtjN9O(X<6sw%9I@$8&nXyxccE+3&U8#B9MyADCZy*|XaJJ$6V)DY^gJihjGv;e zbA@V$nal>v7hP&68LW;l-^hMKwJV?;AYVq{Ut&zy54fFM^#W8B*Z1eiGc{75st5Jl zXkgdZ%usHRSbBa}L;6j=<8GYK>3bbsPh185&-44}yQi2WE=r~M`=%Iu2X#H9_1;|=8y)Wzd!+Noo)E+~RGC$n6!0>8!Ze#fV9sS67WW95O z=Kmv+-=g`O(0kDv(7^L~Et=18PWxIfNYqn%40X|Lx`y1fGD8)v9<4dC!Kwx0;@Vpmu_CGyXS?Ej}>C&LbK7F#^0Rjz{3 z8Q(L~qK`6iU#T&BFkV&vQ#*&`_@{N8S4Dg-_b=XJ=jr6viF_7+il~eICkJbBZ2}+O z9}(HRD#yJJ*wb4xUitieg!jKD=m;IM{bTp9TE9yDC+A+BXCMBP^Hzi}r9Y4e(UtM) zL&A5c=cB9f8~i}lN6#>wf~XChYaqU@HzP{;F9pQ{Uu%u+Ba=FX=+9YY@n!9!T)oic zIcg{6Zd84LDAVO%B7C!*nm*_>=Js|6x48w)cC}Yemkoy+-t) z=Uv3UL7L^Q3*VFFt=%8q?uZ}%W;8Ai_infPup)bySEx#j3eg*MP31{pqnj9FL9@2 zd?AH%6@Z^R&%#gTQ!1zUt1O?~IXZ9kd+w{GWTmt=&-$nQDGX0q$B9{dJ zAPIKav}c8G(P}@(XX(eqK&%P(r^otmB9{pi>Y>#cG^D*xFtT$dlI#1-S^NQ%!-uGekF6e>g0r5<6 zN%xnE$CZfJ#q4mr5B%c(kJ9bhmLt|)wQ|IR{-$vy;}*SoT;f31Pb^qJ(UtqK`{Ens zJMfFxlb968q3}{`SMvQnWKT9~Jh_qVNt*vMqKAUQyD0qR!artyZc_RDGU$%>vy5{Z zS)RXw^F)*m0e5O|{Pk5&9w_t(5+S+YUq2|c{*N#BzrL&S;3mWuh~ABNAouS{e9Q%0 z+xWd%jf3If@i3}a#?NDEJnl;gevbVA)D*EVZM-m}`zSLS2effP?0ek>(=lm{7p6MI z|4(&@|DQTu^?=?_6Uk}5pT>*g^XLNO@y6+RLjHaO@e|)4W_tY+i6i(<$Z!mVUQti@ z!}6KId7ACe7F{wwwrG7s51Ef9xY8bSf#vBQc+ntw8>R7VQhdIv_{F(MeqWN_s>$_& z`gftFSBK?zWFHgA2>yHOeB*v3%?I7_WB-=&zYl5j|=-()?bj?;U}9Bl}TrD(Y?0k9zGOS*(FrJEjDVXk6<@Ykf7! z9TfM}D5o{XSUL5V{8ejj>wVmPR)-gsLw+nT@6|NXX|XT5dJwfEe!?^}Dm^z==y zy}aO@!!wHp2H!k9d&NsfAhuKar|-QcII3;ttV$cmv{}BreQw9Rc{>NQ=A5|Itd0{p zW(DmXvpS}?&F=_k*GJ{v4dIr4Jyps*DPG+Z^-rAz^XFg#$?UD{ZZ$MsgMDhC*Vr!! z!VoMJRgfJkZP5m~s6hXiIBERou{)00IhfkfhG7LWkL{e*acsxz`EB!OQZiW|=|!b^ zyz8zsBk_0CKW&(??-B(a}dS+Bp=lk?v`>`mC8W@o4u zwo~9gl0NEzp6tYPi4l9x#yx+a=?U83ecB#rZV!FqQ9D|{r+%*Qt=~-jkMsl8S1ivB z+AXB~-g7h(N2Bv9(q7_~sK@P%yBo{z7T5N(E$Id7hs&WCYrfyOBh6m)KD0jv&l}?7 za|=ilJnVcH+4~n%Pt*OBRQ@GhZ$dqX;Rb!Z0rCmrI-X^eA2_U^@o(JUy$(5fKIV;@FlRj5m$v8>B*} zyPGnj65~5b$9Gbd@$De_o{rBm0gbzNd|t)z`JTY7sPw%Q_@s74jkDb<=?FhROqAc~ z2pkt_{U0Ujo3!i~vGBh+E%kp4e3Acnx8?T)-T1)MSd_=xlN?E#5%~8;ZExWhrfq$} zcVOG6^!@B_0I7z*!-|K{!}W3cGA(I=zwxIro=vKQpG}_jZ^jFp(+X!@hqy`iY21W; z805#9>^#L6e~*CJhWok66MDJ{5T^O#o=n9{DPN&;Q^)f(o{nGd9lH&1qIP#EzTnR> zoVpdRicEfzm%q;O`c#UyUA$`o0h+(x`n=?$a?5 z{L_;Kj%Ihu7tJh(6ffPn-rWRxC%?4Im+@mCWE<7%0?L^Vaw*~Cvi&Qv-u8tl9wFF+3tU?=U@NxTsg1m2qP zui7zAsP8L_GXm=rCopC`kt2W;(Kr0RP`+;`c)s3*md~4suB?3ozF$UZU-2Bm^CZ5r z@SFfLqjHN!vTumqGpI2O-5eR&F+wLczL5ca$haiV@oUG@YdmfO^M&c{S{>iDzFo%^ zKU?u1=?fbN{7L0~Ee>#_cyWZkYwv4u_K5H*>zaqy_Um}xNi}}Zd;6mKdJx?N$IvYZ zUQ>ZL9qFP?yOOqcr}4=iApUMKKgPer=R~i^=)6*XXx`)e7S8Xs_y_;edqi%a0lE%0)9a+=5E^$=yMooJd!(*U$ky!zk8 z;#Z85%t?VC7>4FmF1K5GUC7GqP~-A;pSDTMy$3(x_&kJ%+PB6#nCvF!4NGsjKP3$l zmifGFf65q!+bb4tDo6g?wlCYat#SD^-nY%`%?|o!JPUki{9>2<5_lH*Bm293N8oAg z6QG&xAC^0?6OL2o zY3*DO_+h$M`y$l)E((_K5RoA}7j~q!<8>hSAUl8Hbr^M*+Mb`qbSU-U_lvws#_5@V z-ThRKH2DG7?^Zk-K$iQ%Jx%;xSNcoRU7?Dcv3kzh2WJ@B?}T^=zbB$UT@OJ#i1LfH zFQ61IzXV)_kHqfswLMVC<4W>}eXr`zNWO(B3pcr+{zmmz18||dZ+q_VMwNfo&V#(v za$c8kzXAL-1I&G_SGcF4-pGE`TLgjx4cqupG+s2V{iEO}Y5+X3Uq~d^pPzS53w+}0 zzlEoK?#*hYHn6(D{!!)moxMlmJVXC}mAa_VvITiXn;-pQGZIhgGm#ZbUUbpur(+q3 z#On2Lo0-V!E9s*`v5!i9B1mDeV8ztes;+fZdB=n*`DNL7N6*KN!;ids?uxRGOEUVR zv68Cx&vyGqAJ4wl*=Q}MXZWfo$Udw3OEOQg;unT*^`~TBY5FJckGr!po%~zzSnbi1 zte;GOu<*EV?(yb7cOHE6E|Xig_`wEWzT@?8Jmz%vT)zDazgWH36SRkA{kyxruf1QG#Pw{wMV`j}HJ&%Px1VO}XQbK}*1kl$ zg_Q5xeBHlGc07$o@0X)-aC_{7X1&!C$!)RcZFv!0LHC;>fL$KXZB#EoYR1+D?05sz zgMPrj@pXZLsqtM?d^oxCeB-f+jofpv%a2dP4OPc;cCxTL(9 zNgpopW!R{aeooV4B<<|0^>@}Z-q8lSM&DeY-nWeCye0YmI9*2de5cjxM1AE0e|NM4 zKT^5yf%?IEznBYu(xLtnbRP9b`5WP+GU(j70M%M`JgX6ZrMUEJj1yxvq1v0P;a4n0 zn)qTk{$1^e-B#51Qh&e_%6MGBi^R#g#s7p?8UGH3+I@aAP@Hd^+4wcIrMRieDH z|GXn|!_G56n(W6#I&ZtraEYY-5nBH~P5+mspVM@cq#KVBxdGWEStR?yI4oyez%jfH%-&qOFHUr z!Wz+O17maq-j}4;7E8~nXC89|u15Ar`@4f;;D;LM<7K^G_@{`z^3TEZ0pwyj zM34TtXrD*Q;)&o=iyjXbdPF~H2l@lvOXF?+aO*EXUlAV!pi}l6{L1@;?K$MD1b(I% zt>dVlTDFcO=a4T`zSDie=11Dztdn>ffCO1t;T*CL2ooLjo?lQoP;~4$WLYl@1Rg{e zd8+@Y+8K1uBIJqbR==_+aRL7v!~qB|xa^7iEq`}~Yme5${xSB$y1YRBF4_NBu<$l{ zDt8RUq4dD~;}6$$p6+-Y*56m<6yHl&LKjK7 zMDHa%@q5q@(}mtgUk`Y|ulLWv_<7!-L#)^1m!~*{_k!Y^IO7=9o1p0s^$4EoFZ@*8uvXIMPVh@(=Ilr}r(Sexy8Se6*u9zT4;(@RaqK zI{L$m03BBASN8KD2+Q@dSfvjP?dQy5zMx-v504kc;Q!QW1C4zVqEe8(%jkVXZ|mbR z6!!T!i1i|#?QV!P>GAPs#)?eYv?H@WXB!hbWB2hJ#wPtN+%uga~}G9ULDv5%-f zhv2@M%p1LmJlQ+}hc&YUD(L?hoOIbmiOB`B4-0lhpwcFaE3un?~SpO{b>0C zGv{`;KG#FJwsT=u(0uW)A?Xh|Hmp*W) z8F--a)_cmo>`xQiJTrp%m;G}ZKjXat@?Z;aYm2t4!}v=1bUe6v6v34NSC9S78)DPrCa=~+bS=<1v<#1 z+|o;8-OZmS<(A*Ek1*;#UyBaneiy-;$G4{TEUjNG`@hJZb^axCpuhI4^LLfUYR|eb zgW4FrpR7IWK@stLqjluZ>{&B*UD~tYFS0|3KfVHagug-Okhh|jO}nh)_?Ni!K*!YIbL)z4S^m`X_(x#;Nq@CHCa1`55;-*?mQQj}@)yPO z;S6eiLazCHid;f@?0j~a-O{ag%U)u)6zfUZvu{puaA_12mEjN^g8rv6i9JAle-`T2V*y!z|U)vzDd>U*p| zRFCyt;Ku`PFRa8bCnos9;_akFzU7xq68usTpP;sczeM_$;((F;v!nC_vtRsr2k{t` zW7vV)iRS{Rv5owvCgI1@eIbGP!@Li`drIYi+D&M|>+#-umQHnjf)EY4;itX5%&9MDZhJJqP|t-0ln# z@geaa34iunVC45S6TJscsPl{YC+Ig3-*)64qo!y-0G}@*dS$-BId2)4o^J`@Z$dr} zgssVbL7@IZhufQ}u+QSv-aPw%HvIO%xlUPy<^ zKQcb#oK#snrN!!FjW;u1Kp*c5eRzj`Lhrjy>CsWXhP;sT@A}=X;fCW7QG7Clylq6A z6Et3->m7`zO7fy3Aup&R$<-AqhmY!`eEP?g`>9EM4n*%Mt0w>bqNUTg{7;S1aRxfh zF5%l0=D_sOzDDx9AAVch?kKbW;&xuI{nvnT3SF-S{x0F`YW!?KFCtIM;v4INZ~ZGp z4yU1mS+3NHy^;p)EhPR7!MDt(YTwg$Uh~Me``oNuk~k0Vf8OeM6W`|*So)KFVr6!4 zsCMvy$`87ZR+S$Tb`IRaxScb`zHd$KoCLkUC2;bS@raF;o_&oUCE^i%t~0K$?U!i# z^N}Vv->2ydBpt;!RId0_6ZnYYjE48K0=|{y&9nr3)$YI>Yua!AnaaOi7!T|g-^Tf` z(tRdfM(9@MlIfOu2K5Ui^>J>I&qw)_DxaeuABE{L+6P+u9EFd_(lXE~wR4DG*LMFy zblzkLLsrpVKpEe{{m$FgpW`E6TbUzE%{+TSYoY$fIPI){gjFIvAy zj3-n%s`su`lY6jNt$h~f+wM5t{o~c|wfBV4{XxWE--bVxCo)KhU)2D{*aCV7HYD50 zNxfsoMezo;-*_E2xz9u8@*0oN)$yGt<4eQlAwKo1$&Enl0(u|GjZ~=hXIQ=R@%%>r zTWX(nJK|3kL9gckY(PzY5tT%ev9NA)fbo#*N^Nh3ol>m_1c|MuZ;hk02FBaLlSlo@fWRcuZMU;@A@_( zlKAZkTS?*TpA0!6blDr{MSrN#o30hd{JO%?C3-LS6UySggg@9J>Hku@#K-%I9_+pc zdtXLc^HY#JohU~$Gmajx$M8%yYkQ~*TDP<3aLLCb`^LY@k@I4@PVeN=l=6?Y_`rCF zp&jm5dHp&C*^noG9p?z%ug3f@K`-A`e%@Qk%k1nt#!qsk zik*#fsUmkmBX;NvOpNN&xm1d;Iq6X1oLj|d{{V!9hty1K51LlCzsMd#J`I%MlR|u*;n|7t0EVUu8c@ci@XP2u;rJc}jT85F zuv~TY-iN>srM}$9m1-0CP``|itMr^tS4#WUxTzgSfN|*i3iKT3aew~Rx?hVdT8tCF z?Pc7+FV%B!zXP6jpF$*O9W#pHLFr|L$Iyf{Z~uGv3-TnY7w~&%eMIb2k<0Z+`(_0G z#gpe5>N&H(YvuR$*zwBuk^KuTy_p@jeh(xFjjzb@KI(_|nUQ|x`!K+@Qm*q)ec$3o z?IM$w^K4xit%vV@V4oR&@|}w4z6Znikfaa`&(l0jYP`ZJ#p7ra`tF6JC*=)z56fqN zGN1bk?fo4VFKPRonQ3fCSUS2{$JqsWL-T|9miKv+Nm;!eU%HQ4rzv`n`d}-3;i0aC%dA4-qZ>DJO6+c_2D&(-v?#f$ZukBsD9d>#FWM85T#w?;n83$=?sZu~82 z2kCxu&;_qk*?rnb%l)&KzN{VU$Lvt-qm9NllIid<$Tc3Xeoy_BNDpcJ(;M$x1Gti$ zAi5<<2Kyw9a~I1%zh!p8wtzqNSIsV<^U@?g0zXi>nlD{X@=x`6KY9kZY@`0aVc0It z*!?b+-xeA^H$m$#dEz7M?@g@IZ*t#dCLPE)+yajyi~eTGy6i&g&&f!>apgNzxcG$D zd7yt=V|um8WxeHJt#LO!-|t_YO`Pw~o+kRjY%Y2|Cvb9dR<9Rh{W|7?m|kzr$#_D1 z#{>Eu7?>pbDtocWO;fbvZ>I4FnNERkmdEd3oonTN{!MkQydT$OzEADaXIlANpA|eH zP=S17epN5usc3jw_2Q{C?(}JDXAmV&`a;`2dnvc~a$T_e@T*fjUU#|NyIXg;+{2sA z5*!_qyFk9rlf6{)FO_=PA1b|x-DOf%Pl}$8>@R!oncVkl@j~>#^UuJZU^+_)9J0V$ z48LiGPpVDk9q%Wyo0_tU-)xcWVlysVB)9^mO3QAH#TF7U4a|Y3ScJo_FRf!F%=s!TVbJK3N0oG#-yh;N4ewQDxt5 zCKYnKK<&<~;rnKy^=rr4qm}#V#d|swJf{nScY!PHOG$?r#d~@T@Bk$Shehyyg5X`{ zY_Hv3Nbq;uZg2LO&NA1kJ>zKnBn^4IKYCv+c76tES_ZhUhAJfaklj)7l2-YJ^g@(l zU$^X|P2+hh@f)5?(GS^|kmG*A5xr~mNSmgMi8%G{D%WT~>bLPeoR6aSlRr}H-mmOD znTLN^ykGxtNk{7^8iz0i$a(5t&eL1RjeqIUQtWe4*)#%jwJ+75UX<7ZU$elh2u*EuZyI zK4&aFsC>@sVcUP&GnWKIIyNiM70 zwD)j+RJ^KOwRny0%gIbpxvc!3k$WAppp#ib&&@(tHRW;UM2n~X$>R+8i}{fB4fJqk zk!}AemB&q0bu%04p3K(C&SaTss1*j?UavAeu!YIjMzH{*yNh2-P-O33~ygg#Ws2u$mn8XugX z_Llu_M!^}y@-kFC5^@?sU)s@=`-1`j-?+(qe*voIksIp$mWcRreYu~~m;gf{d8~0} z)8d7aZgwSJ!0YYgrASLnzY`(RrOvU-shNErT&I-N2{KY zIEVV_6vyz%{`Ezl(ejM&Ddt1*>mh!~>sz>gkMZT{dV%OutZw56I;m>{pom^Vi4*u| z0#twt-BZZHgMn2%@0;DtzE|-` z=bJ9Yc)qFQQ~XBfQ4a!r1JC$Lyurp5HWa$|?h*V)>nla&vj9UNzF4H`tu?(w(*6-h zQ@{6V`bbSbr|Bk1M|Rm--nWt6OXh3$g@C@|@vd%NkLB~?YeFw7Z-WfeOZ2_!B18e; zlAUVtV844;`Mbc`BO|(DIh&nh+kLXAD_~DEKA9Zv)^D`y{uq&Nmr1q*%=6`%E4!~2Ngh>QF>Prthu#aX_{ z^6J)z4n=--N9{vy?=`6W8ldYJ10E6iHQ*7Ar)Zp|pt00QINbt+b!VX z;_F!f)46@W8MGD0B{B&-WnIy80gn$z##fXGKiUp{JH-{VI7bmU7eXhHyoMbEi(nj$ zFZ+VRH{&XPvV&yYBVCDyWOved2;Y;ReOB><=`g*#qwQ0W)1+@R?kL7nR@0t-2h?OW zE|%R%@vxKNMfTIf8W<(`a<|p|ZKa-jn&N9S##hdLQ}w#qADMxCKSR<#-4Sqs`~#Zg zJclFu*8%^)?L_v1Y_W~>Y?CSS{Tl!a*M~h4NV{!y-8CS*cu(oP1F4_GIr2Q|<>)=o z*svc=6MCoiDeR}gd;E4cFj%WG0q_PtpU$0xd8+3Xr5@nJ^|GV5o-wBpy-@!PS?_}m zVFSRP2)Mp2$67wWzlr0F6&_|?m(-$3Zm?|_Sl@#OXp z{c7#5NH0$YTxh&Wz5HJSuShRXjp~U!Spbm|vzf9NRe7YHcMlLO5=n3tW8ou9GyU=sY!L4Avt zqmU-N81F@)clZh0QQGXt@D8$qA4mOPV>V^HgbF`T_%L^X;>mM)oaSAO2Xy6M3H>Fx-{X$t@uOdUpRat3aWdSZ^HJD`L-;q@0ZQlbc=;IMPv|wx>-G_yXOzw}ol56S zf5iS=zi&F{`nmZ^XGZ2N` z{>(uyEf#(#5TE&14=R#9nLEqUGcF_n9Q+v>q2~*go=Nbs{8joYM)Wm^@$GrVn)KyJ zIn&!Ucpv^}I@9-bbHWGKsK}IZ-d7jU3x+_TnF6@bw zQ@_kr{jw?R7vnynw(-K6hEQ(HPN}_Mc}JaIXMATMf7+^BH#KoTOuFj z(LUo)zANt^ZElOjSwr_WrjtBQ|EGgt`0UFl|wB{yrB;8 zAiK_##pifEw{DY2Ua{X;2bz@j`aK#i`fjP(>uda-m`P0Edar!6?p&7&)E?XplCSdd?>F@8{Zko~=k=4s$j1M5f0$vp9l-$thsR$BeS&s%Ct3PHn#Nt1DM)`=UGK&^x!7T{4j#nq zuR3cN)TITk5TQ&LDK{iLn(?>|x?KnoV7gbihvVJy9{yfX&J<90SB$^NPx!@ppWFh} z2Ym6{g!}cu7h{nM(4L<5dmhof`ntcX#jdY%|Ck)`F#`Wii1HZ@b--~O^OxWg!&iXn#$P(i`l4%-!@QKNYTJ@-d9$qwM=0ZbG7ef4F`x zryKa^**LZ3=b+g9VbyY6wwnBeQoxVtNc_k8c7aEk{7kF-gnc0RRrwk1W2hoOAF=o` zB0pE!_V)V{se;VQ?$=DNZ%916H(u);Aa;*)fY?D!o7y+xhc!8)h>jzDmPYT)&lSJZ zPF}uCbX+D!^!x0Q{LDJSU%LL`_a#5=J~WEwgWu7ABjGcW8|jqDPpsqf_azuU-9w9&LXA1vXd&mT0A3?7cVt&qS zwdahtO5Pv&(L1r<5e#~h<))J@s{CZTp&l?;#&Xc`zLEN!BR{7vKRWTA$j{lF-{-hJ z>_126%p?D~zM}s;1NdOMj(7v?d_bG+eC&e@Sf4ETc>Z(!;Z|N(@t@}?`~uJ;tW4yJ}drRVUT)!SL%?q65T&lIhQYr4= zZ^ydu z-7&sCF5>HZ5`3-ki@GkJuLFBg=DiuiWl6->-{v&@eTI{#@rUa1vBY~KzOEg=pnH7e z{lW)FCDOiof!9fBlQ=W|A@KC19pYf!(tcaYkL3TuRxWrf7yPU2{$Pv?JcFOa?OHtT zKSTEv(of@=W&QK_q-_8GHKMO#! zuZ+aWAF=7Iq(giUnE0pQ78uUG>qfFpTd?(Wn{wQySSR&N{oX=1utZ7k{6pzBiu*Lw zNx6;tG<=EM8}m!9|7Ju-?AOrp7t?)xGVbHETnFcDFZPbVeLv$qyD1*GWIMmX?Na%s zaUW+R8K2vx=NGo%de%=gj-2O6d7?+bzO)|F0FRr;8Lg*aBMifR6OC{A2kJceoKwR9 zo#$%5$Z_M{Q=M!lljTMIZLIyJAeTx0X53C$U(vLe5q)X08o$n-Byh-{6o&)&^lY>@ z?p)2EEA^uF6*=#ji|j#xL#TMSeor0JGpQeE2+a$!kL|YT{6<07vxghDfBfNMpV9Xl z)jntYo!GEn=R53u+4h{dJ;yztLp37nX zH@`3P#}~1l3slcJsviw*UjTnMOpExH*GU@YTe*+;Vk7!{7xppW?O%y_5RVt>>GU4- z$M3;DboTq9&*vcDm;*(x>O6Ym)^d)>`WvQ-T`>j%18|2wyiV+j2KX9z!B=IwVtT@^ zz&TKg+mzWA)8lpp^}A5|t!P&?T&VBGdM?>34Z!Wh+4FL1N@w>fo$33k(%DR5B6~Ij zwG-Wih!~B-Gm6){a(}=vf(MlBCi(`<-sh)Q-E=ieSB<2={Q~B z_oTqjoyebswAjZS|7$?MO2--DWAwx0k@#T4s}UV@znD)e$%%&dB6{ZW*!|%H0ET5m z=XTv1;~;t-fHscu3&|lezgTYm$I4CBlQypAL0+Owx!g=m5xE2T1D+ayHCO7F=F9ez zzSlU2z@3BFr0TkO-m_r`v%IV?3Lfj#9x-=`yv!B_AJP18pm7e!MRd&H#T@|LGQ4zr z=3U5hmX`_noeHCbE=^0?jk=){)yierz_%5p^tS!R|8-R=LIj7>1s}b zuHcV|d`!^QoH$)kzYHfJx)NOt7!#+f0f#2(3GpHLP(J)@!K>K$1NKpRak8C)-)xcb zdmP}+be-Ek?6_P`^pyFY;8({B{iO7L8sm3jgx@KgKG@Ym2%QfEkobwN(mnJkzYl(bysw%2 zccdKc)jq@iWAK29?)!sBPU)l2zwj}q{nL;JADR&HWf@0e-?H@&jNZeB`b@t%zZN&t z{>c6UGT<+az`qlse2#w&_@zhAB0o%Ecs&SGUg6<;A;vXesp2EKPp{9ox5sb+j7Lda z+un!rG1mUil zH;HEtxZAe>g!Ykw4H#bo68O@%-hdT4p5*@0v=hj@Ota__rCV7?ERO?gf28-95qvc6 zZRvvS`8?$(*NIl62Iz~*o2fj7{R^FV(nh5wJRSiZFn`H?CceE_!}x5+qsDlV141uo zXZJ1feIH4Gf57ufkB;(PWRD0xM)gCnXH4Y(BVGcXgzq8nekQseS>O}h`@nuZR+&d} z+~Q%&2PxJk{^_!wiSbP)eQMm4wzqb^$Sd0Sr*V(%d0quOe{VGV$+^UP3n{U$5Qk&? zsTiesJOj~O=oO-be!D@Ohv4n+o|1K$ zHTB~GCW?btKbhkOpvyA9-qNr7+i0~;?w4rTSNX||=k+0^O6VTTBQUHR@PCd_o6-1lG{tH<~|(%5&({q*8n;xm9lO?fu3 zL*UoW*V>hMU>X@xAHhmO7C6C z=s73DaoB;2rJePc4faKlJqBop9@|A8pJyC+uEl58m-9Edl#HuP|BYq*pB3ppjsp*j z;`M&JoWFtJMs|ilyqM)fazBim2XsujAn`ivAl`=R`|TRvoG*N6&o8;U?x%FJhWH`? z(!3OZkfQj3g!#Kfn8U!Q84kz+osWPv*d6O+S0ev!oR7xnU<5T%`bd6H3B@Wm;_{_i z-Zg!X2Yo?yjpc*ndwSQuB>ohtz62Q&e^!^9 z{ganf@6)^%`Y%u8f*`DLk7h4CgijNAgk7TJYzNg5-~0e@r2g!FZo5zM`b56nr`Upg zv>W&adS_r5${Kls@9G{^6W0kH4`$nh`HX+*z+_7M8DbUWw( zPGgY3cq!ek<$IF`O-#}&=yoH#6LOR8tsQh2&_Qs;c}THOl#bW+ILHoUJ0A2qoM4uC zKMfInQ5jwLlV8i{t@nER06D++fBAk|kNAn^O?+Rzj?g>)e)82#33|7DaC0Kx^1&v^ z2VV_pufkV1CfeElew4_!{f+FSzZ(+mY=1vYmHq&sb-|~kM_9j@>A(Zv07^^T zkLZT%7cFpuHqy%)?`HW%^&JRmZf7>N6Y=u)q`c5ZhagJ4f~q`|*@s zM(_ynX0qE7{^FpY>HL#%hk`qbSu@s}0-u5cK1kHEDz9)TtS7f5b+g6&tKYcgrucE#U>j}dtLI`%L7Pfs>3srI6a^%2PZMz9aTwKty0dKno| zeBe|6#}3gG%}3&q1y|vy_XUKGO)q5r4y4?_1@x3hAH?r~1Aeluyq52|Y~0h*pPd&b zMEb1DwfL5JrnU2mHebizrF5_NO!&7#LcpGn@ZPQS3)LQhyrurj?13mg;L7?{6}$ti zA1Qv&Z~Q_Ls2{LKek!56#P2Ehb2nBvRq=BTI`G*)lV>&fRO5%W4woYmbV1{T|DK#@ z<5bg(n_Q6%5-0f=)kAt%Z@O_h>W!q*KI+W`ZEOqr(R1=P4!}KC{AV?O*?Uj!s#vI* zd(ECXe`edPnO|?4I;&&nVA`D7^XIlrn;*=a7tFLpGiS5AUaOnZvPDP~Z3gC)`~}D; z+RO!;-aYQU*-cv>J!6}pJ574$sv}z#=Q2to0@)c8G`*N*vydQd@`tLWd zzy7Q0rN&@>Pt_{FF?HoHYL(y8bo8yY$}fKO%do|&|4@Em^-^Ol{?irtTIH7?eRpTA z@^3!+>srNT(QU(K*Q)UCFI-j={yR>1p4Y9a_0j&D{~!O{y;@t!M>%}v>c3Nd z`0MSfml}ij*QBq`uG2=>YLEHRx?9!X*wzc-!&m=JSlNOt&DDP!ZZH70QuQCoS5z;> zdDXk;)+`_WrRQr3ru2s^VR}{n<&M1J&g!N3;C=4s7=4mFP5vsh50F>1`6nN9_Rlw% z@XJddx$xre>~q0Rn;kK=`SfSLdjGB2hmSep;>q9L7Vx|846fpE{4G3jN2b4uf*8^{dc+ zx6=9x5&%15%ItYlmb8vo_0l%G&lnzbmDbh2ixu)JW})kziIc`}JF2x~?y)oH?+lG~ z0#sRhFmLAc*=_TWpW9KfS$k>j#PJg*=WDb)zH%Q?7uDJao6XxSz-Xe1y?-p~{S!-U zrQ`27sdMJsj`pCvqpf|`%-OYGZNXaiz??oXRQfjlo9L@(b8G1TwpnvAj+1-sfQ}gC zqz5~W%1>jid-KQhpWkigS3WHM&l}@+pK|&RH{S4tmtQ}0^{hJ&*>Z=WySAS9gNG2G ztN#1W-Xm-3=n5@cO7njDdiwTN2j6>UZs@u9|Lvxsx9vLalqU~Zx#IQt10LGinID9r)`0zv*7I#VySn4?g$p?eEy|@P&8x(SDC#9kS=fXI;0& zj8~34A@I95-~7efN4AYV_3UFt-z@tk9zW! zKH48}#LEw@+Uys{&i}>A%Rl^Jr$q;j+2=RKKPw>LMnK*EZqyEycaqh&)eYC&1GF@pdg{W%%P`dENm$p7+-sJD6 z7Q3%4f8n+CAFg=W4=!6W=AnDrC#?U$#_6e90lk=(`j?rSG+W*EE1psWt#@FXc~t?=^kdcneAgSmEztdDpR=zHFchUG`?7 zr@nlc8uD-U9JoQ*z1Q0-HlV%}erc8c_B`dd(sc6gKW{y9*JNJ5(^x>*=7QT7)vCYZ z?MXi#{><;LnE9LAW_&Q_ydPb>=XT7SXD05| zI%f2?I|m2Nj@-pcYNB+A?MM_$Gb;V9NUT~<%U2Dm)VL(?@^ez(yZP+j31tZLlvnE?a-v3^U_^!h6fyFB&W++)xE`Jr!37?}F)-HjKGe>=O&eb1gb z@*5`{IH_auv|36S?@{#{*0qCr#_Sb1jsIHo_Z8lH-T%5{i~l+-sQcXm_x^knkYLT9!l$jXbMBlII?73y ziMU1&iS}hEA{^JE5p9p4%ItYZb<8cJ5#pg*zhwI)4+EWwj;9~+x}P^coBZHuo9%MS`+xF}IAclYx%2M*?G<8( zsJnLj=Ltac@Cm`~m+bH>Z{*dhpZem1v*z9MwKFgJVb9&CzP#o9+&2o=gA*-C{}z!& z@w8CyS;Vpe#m~(bbseVQHuAdG5Iv{o8{Q9yXuR(G(eH<^r~5V6H@qK|%9oKWo{+4M z^^y&cPnww4mux+(<9QsP;B${OGKoj~vG4E^CCmEv2KY0N6he6ZczQs5uZPDF*$-xh z;DSOb*Y#iXIqctg0Mn&?USlz;aftsByzP8((>5fhi%mG-B?B<@p0n-yG{xJoanNzWIHMpZKW$tYdn9?LGOI$Kf8YMwaqs_ zc4lVtCm+7&ZGS}PpYQ(sU(es_Nx*)3(}f3Jzr)z2?vj00Hy^U3{iHYFJ9YD(qn@Aq z_*JhT+|!^v36$tx-S=$vRz9+HxsCEs5tW2b1@Aui?AvJfE1;R_5Bn8%LAnR~B8iU< zF}%J?YqE=}{7_9_q;#(9ujXb=AExE#kNLKxtba?-!nd|woIj1(SNRX+!WZCoTKC)+ z_j+D}r@?;b!SpnfnSWl=_wAeJ`cDjBRB=8A(;&tnNjnGMiFZ8$(bFawX{ao4Sh;0diH+QP`8(@vd z=y_BC7i~t+>(DRB;eB;~O^jIa08eY1oo zq8DmM^id5zHR*!dgTICWZ%O*VJeV%fKj}@UrHUT5^Nq%@<14D1XhZvaN&Am12h|?h zSMjyfp6AkWMe@hO&%y_MBXUvpKU=w2PUoXOI-P?lY2(BQz^xj+TmCX4S5Tkr7AL24 z6JoqX5ACuq(;Nss0G#mS{Z{t=T~qB|{Oy##ZT*82{NZcAL84xgPD0(^pQL-sM;4C( z;7#cVTdf^_xB&wuyzDFI7JxqGQi40Uf=A)%U9lW zXOREbLCyRA@|e{(Hgs=%*x2P;AHEg&6E%OGPaQpY+*Qk*eWzZ(^yT&b_@x()`t?k= zc>b!}AK&EqvEGR{fYNrDx8UIOPWjvh6Wcc)dHaKROzYaLYn!F(f3V#i$4(jhZWoj} z|AZukR_PvCj8XMKn21Zjb`d+qb=W^HsJ}BZ5uex(Vv6LVQGMJa_w<@gR4+phE~fHL zHH~;j8>KhXG;D58Z?5SHnnu44_Y3)1YDXb|8RZ9<3iJ;Cjq8IJ*GCc}HrOun+M{$+GNR$2GYx;{?ZtHJj_*@Wf#}z+!JksQkBfVJZgU@Bf<65WyiA-@L zm}A^O{E2RQ59}h&M|-BDa=ga&#YN*rKMLoe4Ci75XL}w`;jHhSWBCX3rhRrH@K58N zp!U}&j4O|{wbL+u9_L0nUYkFs+bt>VMN^P|H5OrQq#L4;4+4-fsL z{6jh&^=sfOO^1^@ixV^7+ zvkL87D&&=B1xOT2GZKFXrRfE=3@4NwI0I_)*pB%#=FpPviESs(3+8s5&@p$uhV$3J z&S)=ve_wwOdh5EQhb>=u=NF$m>;6-QUv~IeU%ztN)x{sYf5#rbeeOrIUn84(^_RBu z{&nqfKbvyfo2zd8?tUk|`9|T5_Fp_V?z!!IHve}|lfFTy$mQ=25lx{X-)ml7pFs~X zzng?UdqU%7KJ)^~l`Sn_C*pih8IgC06SCa-lcp!CUiq`8_tErnO=GX2yf>j2Y(9p= zd{6#hseV|2{5+&rtLib*AEkQGw#SfJ4@Lc;KItVoHzx8<(t)J$Z(Lte3^iZ+9tb*; zn^FHLhy0Gl@fQ=tJKIQ}J*{b5-}>=L=UF}{^r@{^E_X#ASidSDeceYV3EuoK-?e0^0u=9Z5>~+#O&eW8oFJfzq|k z8}u#my8@okj_8}>RZplMdm<4}d{*>Wu@NsE3%b}_>B7-CL`J{qhggUUl$9 zj0nHTFOSi6d++@7f;S6q{(ds)ZNWdUn;GCw_~4>mJB0f?KXF8L~;J-qVEF8eO{!g+3rDq;9K*Y-k(Gk z6X(Mf;QX}2H=i>X@VHDS-A(y)pgzX`zR;zKe_y_g{!LowFYrRf`*WFsj!*8FF}aiY z{FE{Ly)b%BVtmt*;|uaM{yxU%gI|d59#eknaaf)~X9}GKNE1IT()7-nUZUyuwElgX zeqYniY5L!i_AFjan!js{_-LBx`T4ZW^KXJ*r$PCl0QpeL55$)=j(Gce$}l7F?~8Hz zFrlq4-_eBxmV0DZ#`)T`qy^r%A(-K5Qq%a^9Wq1nJ(*IB8PhKp-6RTeuu7`9k20E`VOoy zU6ziG$LXS;!%gTXP*iNu+$U!F-GatS9;0abL&>!+Pc%7rPB!z`gRhQh(H1X5*BltB!EDm zAwXCZs*?^OBqU811XNOyZGtFKKo(I0MiE8D(Qy!U=(wTsp|~I}AT2P1Ix;fiNJa(! zzjwP;b*rnZ*_`=5-&fC*+`i|Yd(L~_^X}(8=gK_P()Tv&5A;pzgvN)UZ=NTtR|-9+ zg`ei7Be}(|ay^{O7}2;)uUorNgPbRV`yGUr$o7?HX7jub zDqXpp@V&`b)8jAZME-Uu-RO4&yi1fXb)T%a(deLXkGQa^@(?#wJ+zScvqqh(_Q{bX$OcP`h1tw!-rvb9*P;c z-d&LISr;Evdj25SzpPO|!BzZTG@2k!ErI~^M+qEJqy)B9cPFph+c7?RfKu+eu;kszkUDc5!Ad5_m5b7 zNY0UJXx}+c&KLZh#w!?(Dm%}?p&@!GX?*b!)n5(ZsXMo>Am`S3-!uBsbL$G2q8Vt{ z)t%eJ=Wlx7wedqw6Mg=|-hW-rts_9_yV>YS?AjD4rjPL2#q);o13%y;SHSEu+-`!% z5weTvy%fn22o;@g^D`>)MDor|p4fL{BPaPThEKEOZ9Z*ZcOLV)8pJbA<%gYX*6)e@ zdm*ON+I9M!iSp@Me}=+sbU5T0osYau|A^Wbk0jRVpBMUx?E?KSvwK$cXJm2A^iray z6nKd78|RS#Aw*~O-!)_XnQDpYfzQ=UfrsRMuTS)+o&$;F;Uu5r6@QmU&dV%m{y`&t zaacaxb+qrgN5isY~tszmwcEIh~YyIZx!?FEvhJaxGH2NXjpp7t*I> zw~=H8J%iF>c{xb*0{O$`kb65w-ddgp;s(qgmXG9lGlgGNKa*UPxCXB`q<(IA8#$g) z=Jk8%H=Ae3^%TXIKtJAxa5ee=d&4{0qd1I*?@EBDi0?%1z>lDKU#R1WP*3a*kNMlB z=$yMEYM8#4Jd%j&qyIgX&g4C6r=9sHg1#5LNc{l!L@$}tO!sG83&OwdkH$aHJCjjv zayA|qKw+rOG#@0dFmEa7V;Of= zzaxV93iTTXCALpP*m2iTb^C5$eu>EQ_i@OYBacYGq<^@5=Xbli2ySTBpV3vmyB5n=r}RYj0pa0Bg)=E{236iXC-S6B^Dr0c zz=PbvmC#7zO5LLUCgp5$Juc~I8=dqko%|;zNaUx`$pS6^L!x}TmcIez42RmMLq-Y>_8n57) z;rADz-n3EFI}!2{IPev()(_qs<&T!=P!-+R{31f}Xnq@>yX2oos)y(Ij`V!<+dH2d z>G_LSWse@|xqr>{uZ;A3!JPAM8RmJ2vhpzHHAS|3xyk-$aBa3u`Q|mt)~;K(94Er8 z?pu#Mor}Zs`qrOU(^c}O9|dJAOim8UBYQ^Ia`nwHe-qfl6vst3+%W60_-2?tPQ}K! zfq}K_Hel=RK;I=7U=YYUIu|={>njh3qWW4~(v-lotxe6&cp0Ys1;dmLTZBG_nw^m` z%!@QX@o$*r>ekg1$2Ujyty~S%(zZ0);&$%FHQ4>A8`a{9l|Qbi z{0Z)F!UL~151@K4WOcvx_bEs`+qfPi(T{T@C=-qL39js49xh+AcH@fkLVCV_L*JVI zzID|Ev~tbB#tq>G8^IDwmxq1e4(#sU5Y7&lUb12N`f&By5splv@t3Xt;b^X^tm*#C z?Q?$8dDFbpF8tgVhxT3nj}N_l`b)q0`Ed_Cy>a8gwvA?P^wbow>oa67G2ae-KFe=CAN&cJaHHR22>$Nf-&BfEm)$afKG0Ysc3u^NMg1||f>j}}5!5F>&o!*a=dvB`f zt6;p|@1yqaSA=HDTmRh<*?%f9`<_(|60Fh{~%t5I-_>t zJOt=z_%8vS9n*ty2mC^Z2R`i7n;gYKr9S!hD5qQUw*e5_Ap!2o^q$%W!GUVulAkuW z^Tp1c0Ga${ozDPq;XaOqBbfxwA8bMd<(S4z)AZJOy- z__tB%l;(9t=Zo1dE8e9)swMNNcy)>|FP(`PpCA{Gqoh2IJL&v{KI1j$L=!a6_T2>M z1i#bKAMvGgJU_vt3Y^_XF#Sk8tEBT5+o$+WC+zWHGW<#4KlK+h54s4ZVxFWQnb++p z>3^EdkNPK9NI5>EX#I&npyZg?w|Di*K1!~8ThZr zA&d+B&HM>^q5gpnh8uSJpv*J-B{5#MN!%7*Im=rr$Gqc*bv2SJ$NRdD?Tu&uYKYfY zoEx-tH^>!=lMvl<`||z20Qm#5{+-%G{W8f9Nn1VF!|!F{`&klik$e1KlTQ^;y&e$l zOPJ^V2tLRDy7y>|U*3O&;kWZT8Omwi1MHtudaTWl(RgQYoVKg<19lg^^MdM`qTG9+ ztv+h6Meh&O`&O-QdIs$8CU?FVTu=Oq>&KOkF&!i?4E~@^+qqQRY1jKrdOx-QvqjsXxH0(CyF>3OzJz;c@J>(APulcNn%Bvo7bB`4(}k>ap#gj^ z8$V};-}&_#&T>x;(?m~9w}XiSf2r%1)WfxJ!qA_ zTS@->bScMQv*kXPAN*Ys$Tik?-X|pwqK+;Sesz*ANc>jk3C8%3>0b4)=w0xk#7%=e zyae4gXvaf6jLYT`>z1{A-AJy*^QH2>;*ZPs%Mx_w`V;NOc96tdG2cudmHE~%en-aoDPZ<70pUTi>jDb%Z`yGC?Q<ljA z-GNKNYe#%L2P?ikHA%~1C(}AL$yKf=c15lHjrE!Ee?vT296r+gBnSB__7lrj?^#SI z$?uB&B=P+161NSerDeWt-RugD=aPL$__y+YjDzk&$X6+6I}Fwfp8@vaesgFb?4f?e5qfmNlsMkZ`5nRT(r($ec?Ud`={?W~U!gDg3QSIR_|L)5p+yVOJo!6@{|uZx z=w%0oTl}3?x}q_i4+n)u?bP}`Xb^c&1ZS5&K==wg&5xSrm-(LTYkD4GpFQ)FZ{K^H zFLtWZG1gNedSB1sA%Cfx>U)^Ut+?QR2i-@;N5Mv3XTbhme+9~EytC*kc~7Zsql=v6 zExG)B(Fc|X^+C<&E8M5+71t0vlz+hebmjQFp+PD?K;JP1+CD(zPwivv7CdRMsCiY$ zr#hVK#rafk=l1q8{!zH**!$l5B)Z62Z&>phf=W4p<|Bjvmf#m9eAIqaui(ziMv zKr&`T=*Rp+X(v*Bvz~=M6~D~oR%kxq3ZV<8 zSE^_BfSk`jq>Fh7!VJr^AarH12ZGEX%JJj%b(cjw&~ueta5B@cOD(R3r_Szi_`Rekz-pq7-)i zs<&*s!>)T*=?|z1u@68;Y#&G*&U@I}*Ze|V_YYDH^2Z)Ey0Lgmll-wC#dIh63{Ttp zuW$ZX-F!v0^Hs6G1NB2>e?u@s^7Vr^xDxTP|0jVr*hlUyt`SVdeDx4ts$30mKNt7r z7bNoKgLzut{DMgCV>kxAkmP@`i`o@Ys>JuZrb4kdV!OiZ4L+ym)!7>{eL%?k|I?1} z-(d9g^6Uu81KL6Qw}BnuBUeJ?|f1`ev~M z*us?jy0CxjTjKYRd$j1H&}Nf`vv)e@Q+BJNc}71dEm4C0<=2OGuxfy8{n4d!q#sQ zW?+0xkLhU^!-3@+jvs{ZBY;o5Ep(~-anhh64|GvS4}(e%`wBgX9;@;TTus~r@4w7_ zHf8L)Lrl$;3k*;CP?L9>ckZ_2B<@hSRsHre+3!2p#)mG+4?No5dtLI)K{Q~%F8l+U z_#7njht_MqT`8IOlIACuQxT6V(s@$6rx#m(GMWTm#&llR_)!-0wHlp(pEnbqZp(1H z!G4$-q8F?iuphiVkp65P-#PiN=r^<+Q_1xf;60K*NOGckFVg3ctkZj8R@&d6+o$nS z{EU7pxV{Ty96~P;V!*dNmwZ$ten?!$=n(CYoJ%`dKiMSnR3G<3Q-CXd17MVgDYIRM0iXpZ==hLH30PGg%HGPLh`SnN_ndG>x}Zp4fhScM9`$U69h-*?@Q09f`U+3LFis&|dkMUa| zaY7Fwj^95o5Pr>Gt^9fj^XJ_$zp@`o>eKh5(4`1O1C$vkP+t3Vj=u|?j~ zIAQW!UE2qy@Zdez1^7wc#8WXGyxu{A7V`nBK+Cv}HV$6V=Mvv=(-Zp@La-9W36R0W z-%IVv$i9UIkn+Tjv`z;7oXML!0Dei1J)wBV1eg6 zy>cEo3hrXQ4+G)9pT*;4yk|ngdp1q)Wjd7ZaH0M)h%pmB0JX^*$*;mPw(|Asns1fa zw{|}4{Hx$F;IoIutLq!Kjxh!0gikGRIDcDpoV1P%I<4dLNa7nPuZ?HHFH`xz{2$9} zm+cq4%e-aA#q*|ghYf5zKZqy77e@U1F6DOc9f#~6Sdij4 zX&rt_4e%4i_~E!pj312;0y5)2p|4{EPgwtCJewWmLfpeHq5B@rW&Y3I?& zxzQx2eK!UFr?q|@7nS-1uGnwdP8yg19x`Vs{7vk(%+(54THddlw?=e57|)ydHk;xsk>(UT>Ouu#UcHT#wK_)iOAF(RU=jHbP-`Bp&Jf{7|`wCOC zpE~V@0>A4f_^RnLB6{i&`mWNu9G9khp-YQ-^6sT^NRLszLTB#wYC!~ z{Z{Nlw)1e@jN(`AMO`lfppsX#0Hi5$vxnqDa7Y5)GV3WQb+t1h^+V<)>J6$s)90{u zrQJ$@3t8@Llko)m<9QDmDfmZxfiG+Db1;bLY2yb}A^J$WUgn2C%6J>ukpbKX5BwP{ zgxu)ic3DrANsiDs)4=z9Dwp=_qf!!`~oksJCRwdqVa_xDL0_S}v z>~_-g11`?TfFH}>y&$>*zXq;^k&xY9<}3264c{WyvD(uv>zbbElUZrGk3etqKB^17 zdlOXO_K4h2eyQXKuwJZ^8_E0t$@jFlF)TK+D=PLn{O{`kXUt!ooX3SYuD=5H_A)sd z$hv15EY5Pc&Tj7{_=0IN|7ef%Qfb_MOz-K199QryzJ~l*wzD$_soimq*dcO$9nLqI zDfUTvwv_v`rJlc7?Gw&tbn&=iyMgWY;LI2f*6)G(l`q?FxA>UJ)0buB_J=k1p zw+E*wUNWzZ-5&JDweAbz6N$< zwSFBGxzbJJCb7Dko8+V zKN)DX(b@0XPX@7Fg2(LPU^@&+f^Ql0EbKtKzn$(Es60)jLgHh#2PlrHd9Cq#=~O6k z*X!22R`F-F-BhX4ZV%%DhV*1=4=Ky!cL0raZ+5&lsD3Tt*V3U8HAD-h5Q%JJB49+wC2BX-UjGc^IVXa-b3xUgGC-i(gvo6TG;y z3}1qW6yqtN?*?%^C=mPC75JQl;6Dwo#gyQ)B`fWGwJ33*mdDiZy_?z%o|XPJ&P{-` zeINWSZs%mtD=j6ln_4xl(fW|+i`Iuke-w)}Z=P!z6unY>P|F{bdc|L;Ub&C$jj$m0 zv@71F<1A8n8R=KVXIaFZ8809g_QxXI2a>x&>+^X&@Z%NFdBZc)XBD2eCh%NN&!b`H zCI}w9)3rU88`()V?!4m9^7eA~D&8jXd`vL-1Yg-b?7fuYE2H=dR1YWO1*Gp7Ulu1w zohSo93eAG>SG*uZuQVI?qmBNm73*~U_Te+5%N-dSw4BNKxQdPk5_D|qREw1^nQyb}jSd5ihoRkBXg8JB zeJ)s-;&EQ0-&?CaKO?(V>ADN~lGg!_RJK?0Q8OFpz3e-!eOnKz(#waVpGsVf;|AGF z5`1EM-FX*^TnUsb@V3 zBdKTjgE=JSp_Jpi3AQgmK2qMU?OM5)j;LMN?L{Off<@-v25O&V)DFtnIka;AI8ivg z2TwW@xOxfx&L1kiJy#++q>1MC$ zIk&Lq0cTbIIm;o?8|R;UVMgJ|oK0{*PRjj|*sX3bC->X(axJUfRVvDT)FRigRj#h} zmolD~@5#0JFLEt>My~k}%Qg32xn?huYp_PHybq5!U*m}xI%k{or`>09kHN@9|*sNX`R1D{!`ujohI{InZK}y=FzczYV(JVYT?s!h!H=E#C}LCzt^lc z6MVL>Wuf!~6fvL2_Cu1-*?y?wbEoGJyDaD7qv5>_UxJ4e&nxoy#jcFcA@-INUFJ3^ ze_)|P@G0j5W3g59MB;U}$<%J{?K}@f^wZ|1;D0E5E=T;NkMS=44UQ>iI}eZ5eoop2 zW&7J{O^%=KEYK zc?z|1M(H$QIRnW;ez^xoSzqJI_GT4d-Jo*(NP)A~kB|8(W9wW#@9W07f5u-d7a`|V zk7XCCd{p|bKrM26ulxp`3A%{+t4rc)!6NnN=lRnABK12X zvtN_;-z(M&h zPm5m2_uBjEety5A7j*qKsTXX0!}LPBU-Uw{U-UwHgX)DNHICAueArAcjLKhp?f41u z4tgQh2TuKxNc_7v4(uH7-}bhNzX!VJad~CEr}awGFS-uMS-)f_U>|EuIa$+G?kCDa zEl(%PotE2vK3iuT((*VDT-P55wLDwfj@zc?vL8E!6X)g#JlF>iPPtNT)NBmmzi@YI?V@I<8w@yArv7Tr91kYJs$9{3i^qePo zim#EJtHhmbh z%6h)qEoP?{Je7~K&JdicTW+!=FLeJ@U3tiy^Fv5>JIz3z*u5dw2UpcAgX&m0MKNQE|oY%&3 zG>*TeXNr9FCM4o-T|!@S?u%<4e>1*eKfO3d>FX58k?J^QloC3OobVf_UFlNzEsh6* zaA8MMysXqi<2F7_E1!vd7@Vr{6{qKU$9#nI;-GJkT_@|hrHt$^cmCOmPw~IYu0-)c z7y1#wQAAv$2XYe9rbKWf(jn(`lyhZ~cPdxfvA&M_h&PqFUM@QzbSd>fB~qT&a^#oO z`;d2K?horYT)$o0wQ?^zK<&Ej+yKE7EXtK7epD#SeSVwByS)18@M3@`L^zc$3+8uj zt--qoOe{(K$_dx$bxnh7*I1YLq zzAWmB-;U{%eQa_Quh4iZ`dH0_yf(MWl$kCSrK^s%;U@%C6Bd(7_@xhZjK z=x5c(!78B>_eG)4I(^LiRV6o*`nV+Y2~L#xS*7&J@~k*f=`$0Q{SwBTL;H+xkIy@C zZl&#)xk=Y6IPb1F&gj!CsvfSDkCk>3>l9d#XFJrJn2?jUP9e`@IhmfIbSmpX5NATC z-aR@VmQTe;V><0k(5Z~Oo=#@2eeL20yX9Y#qcn zKt@cTMO;@AeYSMQ^vQgO(TTi>^?F+A^Y6XBR!5(!l|H}qD(N%Q`CMH?pXV!m2Ejdk z3D|Mo6zMP9D%aujLW}3d^l9JotEErpvHe(CKMSG;bU8)jrzg*2y7b4_(k1LGmY*KY zC;iwjY1t#DOO`V+pGGQYBh7Q7{b};v2JUZXwj}U?E?9nMO@68#(eq5qzQuR`h@bFV zCUgip7VB(S7ZE)j>GvmbFq8TxEcaW0C)Lvftbbib@}tU`UeVLAs|c=kh*Dgkr{*kE%iXaQl8fGP|BgFdzsEtTHdbhTDg}iQ@gG^U#5P7Mft6&r?<*|M)h>&G^MlU zLT7b)n){FOTd${kMGj`TAK>LQm1lWsulUcVrwfYT{EgCI)%O|?Cw#`|i}Cjwi=Q_- z%j@@3u=a-bDtbCE`^it$aqTRWWnS~Qiu}X(D8&!CQRqz1Bijl64c&|No~xD4#NI&| zQ0OdIBK+WYF}>HaA*Qo?67o;vWWD^`Rp?CVTGs_|I+f5_A?^HgsQILQDE4jTJZ{JF z2p6@feV7h~?!04EUP^qq3{A}PF8>_C-GaZB?n0rvq~G$tM*pr=y8DyR-K*BWYZ>pD zE^4m@jMGQ)1k=CHn=JihH4or?)4z9C>)#fbMLT$XQ2Y?Ikd$wncUC3e8tL81LU*$N zcohB#&ma0@|D*-9>ye#*iW^MO-5}FvJ>Bi5bT?D@z)9Y|(OnT+44Lk99r{#_?`!@Y z`W1N`jS+f_QbupX=~v2E8KU@03*rMxZ!?wN{dHp4e#+|*P?q4s zDfl1p6NPs?&135u;{6!>UEr2~i1p2SneY6Ce0_!ACvJI{&5x`TwMN!{-lKL~u?I)_ z^tl9|3P0ELso1lP>|^Xd6FzO=w;@Z1@fGR*vna!PKY-%&Vf-}Us}K^tT7Mk#Dceh^ zpYY2dFiG#V!(YV}_DDPPb1o1%0Usk{S_E3wi zyFXf^7rtq9;Mso6q+Z~B^@?6FJJahFdjxbV_Q);_kFV$A#QfRN9}9V2Fn#o1>yt6P z4b|Ww@`kuxJ-zMD{q58^neFEqtzM|Ci%I;!;#G)`t35KDexUe3MEYS2czR_0@IOU& zlCK83D=Xb?P`cywXE^Lici7h<{E(!(aud1}e*@FSa%z(4hphNpIkYeOVUqM)7nkS! z|5oHo>;N8#Ux(EXm3H_%fKkxjq~YnWq2CoL{jq)+bEME8R?s&qzF6+IVxIdLPK&=4 zSw1>9{P);f_qTB+<*F}nyr$n{haH1HE8k=H*US35zn<-S=U=Y+WiG}GWsIt%Kb)_5 zHuhciw0!qv5%dl6CgbmcJI8sgt*6KOh4osi>3NsuyaUk5GR6bqr_2wgXI$Z8!B6oL zy;?j+_oX0S2wT1NgcuL(kD@)cpIZB+ymgJ?JE!_g@zrPewmdk)JK~KOWHpUR1%Rx!v3Tl^C z;;U@ORn|MD9sri|IKC?7h%1yNu3+(1ZP&`<_^QM`>(@JD`)-rY-wUG0U$H#xRr#|? z=nA+dyYFj8ziMCRO`h6%XPrFd@3ywa@&AC=nSeQNFODm~tP?tY-PSq9F4T3-G4B`t z23>{;eI6>kwKKg{)&V45zw;Hw7n~FOL-jcNWjc!aAw=Aq<6GV&u_q<(!bL%(@JQS{ z%<_G(1eDa{KjSw)HZ#N7UZ|EU-8}7>i zi4wh6)?Hc8VBJU8U6botw(go-*RpljCBEzIJZH!94tAQr z<<;(6jrrYkTWj=XQnJ2_5nZKnSLn{x|AU;^u?VM$y!14_Hhez*n0Wo)vv_W; z|5Rxw5nr>of;Wlmi>m!M7GIONLri}S_urJ1{y4ri=BF|JJy$DtGa{cO-JcZ4Iovsn zhlcC_P1gNmy`t;>+r-{M99Zp*jO<%Cd&Jqk9>`0`W9-){k$sAN^}XV+B=^-r1cT42 z_Vs8yA9Ts(vY#*rU0LL2Iy;DR{A%}w(EfKWPhY0-1cnQCmgouKts8s*aeSoMaj;|O zQonijqk^!Y@txwWnxA~D$Uo2t!vQ+%q4(X@QVzY%6?86u_&J&+kqTexzavYZh{5*d#|AxpH5PiLCt-el` zbzGs?t69DemZWGr_|KWX-iztF14Woe{PyPiSYK;9dd?B_^-RT&^cPt^IQGwoz9zp0 zMyM;YzFrc`%>kKLEXZNLD|uAfFV0sE(ZAXA*ya-sA9vF7y10{;*TtPuA+-zr6B67N zxheb8ur9Vu_$jY>`KSdw`Lgp>TtW42tK>mNZmY=AJogJZd_CyNN5gqHh1v%tqo1~2 zRge2^CPyW&s+d>$usoKw`&Ey3@q3*{|F!!W(IUeU+p~Vr+GYQ<5+BHithb#vIU!eN z{mNImt;p5RTD^|>qIpd0YiwH)^T|+xPlgEpu8vPycjs~K)O8Z_qkxyndKu0yI)~6D z-`xvN&8Ym7{g`+lb_f#6C4Q{?6uP{b53!n25OR?ur9)J z6^wfeJ?Vtnl@sjOg7iZ-D7O=VsD*Rc)V(@h;=M zTgRD0{FCU};Y;I0cQIWTC647{x;{(#Ye3f@uf;t>Bd7Lp65lz+chTgCo)Z(}`@tGH^5ew#|7h(CzT1A=0ME}@y_~LpV?mha$6c%E z)W-Q2tC+r@YSd5u41TXEUFX=2_uMPRGuFRY?qouV%X)H-vU7(LeC=S*&CJ)LFW`15 zp0m21lf-jUUguR_=k**JcXKtK+r8THI~*TC{L((n`VoA$1BV{N5!BFidxzDtbtQ@G zI)}Iv%ahvkP}(7=cF1@{p49bMICFG(&+Hhm|ImJjjW3mglq9!_v%`i3FRSFFD?-sAJhm+&~EM*S3y<@aJbPiwxq zuG3*RtH_NYf$w0V%9DvIPkK~-OJ1Z)O%r%xd~+Oz&vCp7=r_9s`#TuE^AdROkH@F^ zdan}CXV;D&`bqG+LCV`NYk=nqV>s0AZGz|6|A)Om{EK$`nBH3#jS|nCS6HFzW_b1t z&z{h=_dfAgp^e$Da5K2P3-W0$**CUdy6`@opIrz}jGiPrE^s`lUWePpeoR*4ZLuGQ z@3bf4DBIZnr$i*uv+?6B=~~RXSoM|zFqv2%xv|iXA4{~ z)j-#%XXog3OF8^0F30|E_^m-;V)KQ1~Xqcw;z#H^714oZ;ZEz~}i2AJYf*yMy}Gb9u&yPsgF#D0sdMSIoTg}y55nJyiP!=-$zO0*X+!f#qP{r!v5XqSeJPKx{@#||gNM&%(ZCvOBwV9JHYadSTo>iVvKpCG_Ihj&J{i@o`?{K%q_bgsnr?`mOE%Ve{x3 z*y*kBS)nSWBaGa?VfzKZis zx>AE|H#^7QnHf7Nmb*O8vE$=$zCqgY_TVex5G+sPb>GqXD`Q&|c*wM|pF4Jc>qpi# z#_kd0UDjzSZUebAw%h9ERnN7_ItcB1VLexi_pzAs9YoLAA;NrE*I(h%QQ}?o-Kg>Y zmRh{$Ro`W|ihYsV%6P{x#2-=oACtiUd>e1E0sdR=5xH*oAM+0BcZ{ub{5#J(`R&&N zf51fzKEg4M-X9Ad=wm!7{!eXy|JWXBQT&fl{6E+t_#g8x8n4-%=W9{3{zi-cVgmog zeU$F^X+Za4uR}l!-AkSw$mnVLK1oXQ>~abG!=K(P^dj`O-tdxdfd8bw-lG0`(vMO6 zSL^|%$Fc9R_7(4!H^6(@>KWa)B{+P@S_#qydFLFb{l8b@I9T|jpdE`GkMkDMg1-e&PRpcgx{Bh z-h&d;`v+_B;8I2If9zMSAIS$C`}r8}{W=bT=d)JN{24os@$Kr**5YIA@2q{r`;)`tJ%;BAtDogK7VL+#z*Xl@a^Be3 zUo`5kDEr=;rq0)=_ksObO*m+9m9{%KYOqc9Gj`L%@a~x>w-)wxr zv5If;>tcEb9wDD0w@#Ah7N1J`j}mVk``_{S*)Ifk*{>M~^MvI7IM}A1$a(v&IKwdx zafLoEZ|Xnte!X!p}? z-8T$#Q{+j)uYvt2a>U!5@NZ-tNBtX&_E+-VpEz%i`4r`1hgSTXtz@sp_H4ahBkLOG z*TmnUORY%wHC@$qx_bqFjrt9BeoeV&WWPq{!!y4|;b`dB>?L?ZoFDc$)*Z~R8Bo8g znO}1V$q{m^h%U_^8?|3kHu~{4RpZP2nu+kUdq7|CYij2ug0~5H;aw#1B#L~lcW?&g z6?mG6_C2D{IW8nU(1AFOwl|`m1ivK|zeV`Qfe*!R!8oa3oYNEH3csbD+Qm6Pa_&Lh z`kR)gY~3)!eiyk@vaX}&x-{DFe>(i~2iQNe{5pxF_?3PC;e zPefc}H28e|#xL{ZkeB!}#*cwHTbG;3ejV2365|$n&~Z=Ptn*%iKAQ`_uZ$Kz2j<7s zuY;?<7bW*idY6h`Z?KPJr^&bU(d=i2IPaj=&m_5(M(=WtSJ|H_ay*e|DRBu9muq!X z;G@uvhhV$_~P%D z|ATYTC|)?vD-l0d;#Z!m=g?^YESG>v;#aV7MD=uEN0odJ6LBk%Cn;}$-*XvRPpq7; zZ0CWnT{!M}(|5sg)yuZeQ|>oirDidB^-mR8RMJSMgn`hVPW#yg@xz zInw+ymsulrL7?Y6n;k6q5Co_62l+$rgYLvWlJl9c4^QFCpQrIz%?kv4Tp#dZys$Ue zzQAAEL6qYs{Ah7lmy>g$aNLa8o4POC^5b1W*SCwZKA*OFE%CZH^@nE9*74AJ&4iZ{ z^A-B(=J{@2s`i-RH_SXN`1QundqFs6Kp(hYrO&&HcSw{^jsb!poknv7tQWseuN)?566Y;&gs!|mlFAs%H1P! zB7cp_A^9G;oeQ(GOJ%;-ywf&4oyM5em z{!0v>U)Uz|SXB94{FKPM;-^IZNqoX{koznb^LJ_aT~aUqC6$Y}iCoNGDdR@K0eZ;! zdEo$=eEuY|aUn#!-s}>IvCK?`EB##Q| zc+}5?mKg8%>Npth6EFkY`xNhYE8bQ9DBh8_A$XJd1Kro+9b%vHzJD#=Cz$@vzBYKDFg<~Xg$cal_!!1J zWV^?7*;0%5Ia*)qHN`vDGHUQ{{bm17ct;-QhTCXYHU*Dj={ND-R=_Yhf5e1U-UrpfsUL9xVHNyKpCh+j1 z1l}`>_wO{o`?q3vMn~^oYtUcj^}>5WzE9;mmiO%E-3&VE!uJNR!xh64eURNu`fI|F zjlWnBI~tAy+a-11-FpYw3lkoT$HV(5COm8J=dR)R{rpz37Ydw*iZIQss*i3J{e<{B z$#RGFJ@6F#teds`W~rC^gz7EH-*R4`>RE-`&h@Co9yRxpaYd^<$>u5K+$<^dFbbzcB4)$;3{Ev8jy>@?f2iRqY$p1t? zzWCcs_gC9~_Mjwq_Lm8s{bhpZI36%Z=pp`|u;SU{?|$N((027x<$KI=ev{v>e$+Ib zpW*gZ4;LS*-A}FYX7f{q`L2oNZNhUQKc?AtP2zK(OB|ju=k(`5xe(zzw^oQz~MxOXjalgR8Tzai#72D@^ z@od3&a3OZCUmmkXSyVK z#CM#@AA1YVk)rX1t@0jbkMZdHQg0`ov%l!e_o2JfR&O@fOZg?iqnzv4u}SzbIWFwZ zGPt7jJoM*U`xx&#)4etDoaN6{|4`tFWZ#s-{xrtt?!Lu$<_aI-+{gnl4*R}^V!?yz-0@I59He5Yj`E_-$Y-*O&{;rpw#cmUp7xPA*9Mzr7YWQ-S) z|HZ77BTnTBz834aWFE{eDT-YV{!_j2%ov%6^al0gf6Vjn{ZaN`%qI3Vq$LjLl6lCP z#6A++UxIy7=nGI=9;+m+?F^^$0e-m_`2Kw@J|?zV`*N2 z9M;--KIb<~oMPiD==W^Q&bzZ9=To)3L+Odjl-9?oN@P(37B_iCACI@pFFj#3j~# zhV8tGi;R9UqGvHcqKApcTD@S5z@d4*ZiVg_DCqj9AV!D{CntahG!Ct25h0JZF+z#pQoM$YpH7NyM2dzXyIRq`k1 zVm@BxIv}iutzyT)(Z(DiA6or|l$R1>JC6ua*3T_IKk7X3R+agLJOchFCS*R-7bU$O7(Jc-!8 z>*acN_MO(-nH~^(O87l^3$N#6mT^)gweQ^VOozVK!}`DGA@nf)cBieL*wgVkvg5ps ziC+;uOpXiooxvsc9ggLe{#p(aJJ0q};(N@HL%~_VA7U-|kzEa9;=BRSr=IH(=1TNF zzKdw~o}7E8`&ofz&Bq)gdaiOV)A1}99*yM{`=t|2zq{7o314|n^EJ*-YkOGik5=^~ zeLeq3{5rrV_Gp;p{Nsra#d2f}^+)VY?M-}2$|pW6c*6f1l#d0DA(K~as_$CW-g{o{ zz1AaCFHGTaRQ!MLhuRgn+ameVzCR@M9p~8}B628uh{z?$->t~2mcvzE9j@}q)~We? zIGCji6<*0V2AQh8rgnDgfar&o0oD%%(Dy3jo52s?Ec&69@sW{y_)gRB1%FW5@xED$ z_c1dC@BR;@-}Eo_YUkyNzM}OU(i4A;@!rDyXx?|H#HAseY=1sJkbwE%g?he@k?(9c z-KXu4FwN!IXw27Qo5W)zf7$6ih|WFZd3;3bflg_@ZG0AwD8zQ7_-*bfvm0mYeDc1U zPONjNo_l@ZFMy6=|6)7+pvnQwTTJS)7GCEafi6ssDP6P`1wTc@o36L$_e-4V-!X=F zSr5x&N(dg8(fyj1$DUQW+VYI#m*$0@oI8u~+u0k_6XU59WkUD<2W1|-8~GaZZ>!PK z4tbt_NUsSxY8fOt>RcGl1IN`|v6bQMTw(nP9dy1mo`0cR1PmCyGpt^#(vSM~ z*3SE>&H5`o$ng5U>SyFL!LNlvNZ22F&Fl5m{>aZ&ex94KKV;ro^3VAtYR6OjXP)#1 zaNCtN`_AHgY`1nIzfSO6%*%Y6-TL{W+O3BxJs;0_KwC=BEZ;ge+xz|*YPYIB%IZ91 zm#BPNqHyzfaw>MKzgWu`C*+gF8(f{;s`eztwU)*O`&H~#mY4e1#PG?! zMVA%5PI8^?*1XY?;;W$hFJim(qpUyG5ADRdg^r)?(az6U`&riSo!gCmEbm$T{?6O2 zp6VG=K%F6FM%>@WGQW-t|sxQF17^XXk$>xGGWPU4XP;8lO2 zo$ww^=Xh%jKiXk)A$uX(7QZdyYKu9>P@4&CiPwf3J(CDUPAl(Rq#a( z5C8ice#1ZEQV)-ZMtF`$YVC7sZ=_#?wlJ)kUxcC76g0ZI_WtdEZ1jxEpL~AOlTDsK zHPZ8E&VA_ik)Hp@y$_Pr)bI!R5G!i@yW`qj{-g0z=bpH&W6Ma-r+#DONll)gJks++ zR=jZhNYCH;x$}Q9((~+uFFiie^KlPeeD_Gt!+rkGGmV}b9c{8d8eD%h?Ka4nm;ck| zB(5|3>oE~h-QlayYQnI;p+@zooZe{kJozjJqR|VD%YC#pRBrs7pCp+tSv~jlOSUD; zs_##{X!*KxSFe3rby<_Y`zyUuQgxgOQGa`p25aKqYgpzo4!!+FcY zbC)ltTl#bD!17^UxuUu(iSO!X^!E)n%z_`;_;>A@vtbW6{yo0***7(QI_#4LC@tHQ z{MmPY-u@o$y14J$D1Qj}f7_HOj&Jq( zytNy`^&6KhTfV$MepK^E6?}|lqAAei%C&34{+0dVnx6jtb<5YUKU8q8z%E<6X2ZI^ zWpD`GDIh}`Sl{X+`__Y0L8lk=t>3VGUAS`1z{U;X1sm6I2$!PPhH&+AJO~e-6E3}E z!}9f*&^7R>>V8+Cq->?9u35ivU|{XK4K*!~$dNSC_8!te>GSX0<-~Dq7rg(SpAEKNu>ETfuX{`Pj(;1w_t(EOXRncTOm#a|f7i5s z!ZB~Zu8Sys^ewykRYn-l$#^ZUh@>{RTy#MYk-~9Ap z-+JO*(|5f6m@_W=ubZA|KjF#N)<1Sk?^=N~_3HRPZSRhBRT%DXhJc0)tc}Ad9r1VX zh)?0+HhDhSrcG#*<0E^ z^2B(6*ZugPzPjIG2Ob~p_NOnj|N4L@e{jq-H@i!2{n8Kjyk*iZVHGv|vhGmAIv2%( zCL+J%CR{!25Z#LF1dTJ4!4u@qMOdMjhie+^`iPr3*aq(H(mdjB7P%1mv{*L zRKz;*lYEHka>PtsS@*r}4%mBezgYY|VnlsU&N;F7x>7S4o=)svAb88Eum-*^-Jd=w zGn3b4%PCop*hBsIG9m%&pJsKRiJh+=T%~cAJt@wDs6|aY$H5m>|NkV3XW4$qjDANb zlh_X~c){p(Ke*Q;aTcuKe;@6;_&iAY4nQe^*Fp8~4R0iv2yXbzgW_MydZ6KDo9=%s z>wYqqn(ywSelTM+o+AzZ^pWmoTt1)6;WNoRVSabza+#;2tv=>uGRjZEyioii#69}Q z|Ly7dxQ6HeSGOxldT_P*BUYh%sz-D)7WV|V#zovN`rO)uZ%6)q{Jxc==;i&0S4Vhb zRB?b;t0&PvrmN-`G0wE835@Q79>6GtXy5Y3(FIM1$-m9) zC>?+7s`Z~4DriqKO8UR-%lt-eCeOFY5zv&-S4!m#;%+2w<|zMSJ15JNFPc2j@Ba>| zTsjndaU1GS)%?94y??WFx0C!iQt#ic^g3Vf%X+^+@6o=%k&s7Lev;B7^1(cjYbJl% znJ*gA{ak(Tmnydo)BX_WXZQ}+`zsZmBlI4z(?06wO$qp>C-ISxg9dNfYxfC{PUY{> zDtGa``v|5!IUnp8g-`jFs4xxLj&lTL{dtbY_Xc%c_RSLCE0ZlW*FElj3chTkZd5P8 z1Yx+X-xS(ejptv`@<`>D#wFdcpo<@;$6wJG z<5;d4Jiv!Xc%G;2-C}Si+FvkAyaRs0JD)=VB)7-f3|Pqn?UNZ&KQYXt8p zT<>xBpwe+!^-=Qu*K7VwuDqW7NAv%s#QfVm@WT4L3Qv6hgz9t9>2T`_?&Jn^(xr4_ z{Cpfpy$AiHdtL`>u%1vSZz7d9$BVpiNqAl>d?EjX<%!8F8ZXvu@RM=>0&q{GJC=7+ z&d+f_nQpcI<7%H69xL#2rTYI=$CD(e{zYi8`o7`c@85m;^$j25!AGyTgY5K%KRh2X zmhq#@jvZ|HG9ElW^yRZhdJgz*80q;9yBdhn`G39pit6VypUL0#?>^^%TW){;bMIYvW&iKLd}H>&sig`3@x49& z{Hv|we%kwm_Y4XqG7O$?ZtAE$LKg{zG zWo0XWsq?U#p1t*sWz$xl{O31)e)hv}>-qH0&c12i&V%;7X!ELLKl4$-@k3w#?oaky zHRjUX;KJjk-9O{8eP1|k?cw2vuRr*xvwpC2_1a~t!ZjN&SV{rF_2F7X0N0)yuIpQ~ zVmb9eb1-qiS8jgj`WxSS{MI>py>)5#c?D{rtS2oigF)*<1hly(`~yQOAVA-JX5_*w5~H*_uDR<~7VxKAh{nAHY?3j4hwHr>q z=Bg)u@#cdzfA7GFkG_9Q=J(*#X(znt!po;V`@7HoVD;~|pSkI*!}oVL|MlTZj{5%g zh39;*RGH02_U~?HcdGr1^LW`Wh5cG1mxdq@$)202cG+}&-l_Ko>HQ?V$NM`-zbEyc zmG1`FNiLhd3voXe_gAR?Lpm+rZ`XTzPW8W`_uYEG2=Z?x${Y2g?V@(AKR7|(KOXO6 z{kGxz<|i7Q9^R{lGo^BQyOa2X!S}TK8EL7HtUK{D)vxrX>%IA<&BoCTU*A@^wrjgn zbbJ+fxPODg`u7${eXn2N(>RBBh2D2*J5}(p-m&}RRPPubt(;I={d-w@26F7L)i00q z_jjl6Jbk3+o9{YjL^(fa&G>)oc>8ZlBel`rx1n#O=fgbv2Fl7--b_x?+QVr6S$oZ| znBS`Y*Pn+qi{>v?*XXZ)f3SD#Z+`y3r^=UKzUMwiUE)1*#&+*3?>_0qJ4)l8zv}y^ zeEA2UkwcC?`mC>v+vo9*e<$8@Ru+@;3C`}<@ZB7Zlf!oKV9ehk012e~BhJKpc=4pux;zD^J7ll-GN z*Ho>a*82nX`Rx@s%KUBjp%XlzjUIx3hSsNZ7HJ$AjhDOu_qXC6Kkf&^<+z0ApQBjf zfoOx<*+<(UxG1g{;vVhc|5`cyMy7p4WTCB^d$-1p$Q!#qr; zE53V2_}msqd=@jp{($a(#C}#QpT_fhqQW^(_PQFHX+oa$tnN1%`O zZ%+2{mGNCA#4_ldAIXc$pC@=J=y|?z-r!w~moO-CyI7Cg!R>%ii0`xc$uy`z!HyeLJTor}=}q(39Ah=2;(rhnO$ZDhI!5{GXEVBMzxtK3nM%HWJH&bMzkdg|8F(r@EZvCHB9_I+p5- zl>n^=ynd$1;|A}aF7@1YeGihIH(0JdKoyfl{RGybmPV(#2w z$$gc!o@e_iy_0l3?<8H%?bNm zo;Arc?<}(u5^?{SZct8iQ&Rm~c9GZvSofQNa-6Hi^pr;bq_-fjln!S}9zWir`G7w2 zNbeZ^L2z-qbY2|GY1{YL#C~`yrWE6g>2-+{{k9GJZWD=PBHbT3tcJ`nv4*GJRiZ^J;Wq>$~|aN)K}rdQkMa>0PYL z>VAci%uAzvwxgy8`A&707cqS2E59#Oz5^}v&^)XVJmN30Ba?Q{d5UMFk21~&A%2e3 z5AIjIgHF1sy%6^_|G4M&%er52x$9;6vwGz~;QC0s->;wXgYAo~r_YshDBZbwZt1z$ zC&=`Xzk=;}iYSoX;y_nqcbuD+^BT{UeV{1M4pKQ)pnB+c2fq;_Jy z>jGVo{kIf!Mf4ScF9=@e%YKF*Ch04u`^2=M6umeD`(En%6u9hZBu&;TlpDJe~$Of@V*}RCxbt~obmrE&-HQ7#rR=9MDJ!- zvwomscRj{`?YTa*t+bEJ^SaN@_IulRb864^!TBdD2Sxr@`pInMaWp;G$IhV*^jx3J zaX-(l_$cu^FH~Eh)hX0W*4VgA#yy&(?i_iMyoey1e({{&?*>@4NRy z19S3Y&w6a^wbRaeF;(C66(}m#7dPB95S#kLy@Rr37ZJ_nXnxZ6J{j2rU63x+Vh<|1L$&rLVF(qLK zgNJ%VkBk4-rTLDb6MG!bSsqOXCdh7z>qGxfWLP8*(dyOadr$?5TTV5337}aHYP_^t z`&pp&4Xvusct|5qd~T|5=Uq*cxLzvM=aSDd$>4{dNA^E{V)tvm7S{}cGHSPM&lQei z0U(bb@5^`uUK@|Thqr6vgO-vXl7F`nCLJbM6Q?ze&6yr(`lc*`v3Q3$+E z!T*uIw+%&eVN0F{ytUC&jAsU}_{0J0$0<(yxhWQh^2DFp3vj|WafTnuf5rMxxd-LY zD{S8--!IkLVsQK6@Wcjr=3off@j_<`)s-?Wc9@w(h#$Uk-e*u5c6uCbyc!^P0s28+E1aZ+e~N zaNMnpqrr73iQM=_{o`L(UbE?-%fGSvk-eAq3|>5U-VdhEdh}Dj^(SxM?}a^{YP;k9 F{|{007Ht3k literal 0 HcmV?d00001 diff --git a/core/lib/l1_contract_interface/src/i_executor/methods/execute_batches.rs b/core/lib/l1_contract_interface/src/i_executor/methods/execute_batches.rs index 649a7ca2b419..5d5494ca62a8 100644 --- a/core/lib/l1_contract_interface/src/i_executor/methods/execute_batches.rs +++ b/core/lib/l1_contract_interface/src/i_executor/methods/execute_batches.rs @@ -1,11 +1,12 @@ use zksync_types::{ commitment::{L1BatchWithMetadata, PriorityOpsMerkleProof}, ethabi::{encode, Token}, + ProtocolVersionId, }; use crate::{ i_executor::structures::{StoredBatchInfo, SUPPORTED_ENCODING_VERSION}, - Tokenizable, Tokenize, + Tokenizable, }; /// Input required to encode `executeBatches` call. @@ -15,11 +16,15 @@ pub struct ExecuteBatches { pub priority_ops_proofs: Vec, } -impl Tokenize for &ExecuteBatches { - fn into_tokens(self) -> Vec { - let protocol_version = self.l1_batches[0].header.protocol_version.unwrap(); +impl ExecuteBatches { + // The encodings of `ExecuteBatches` operations are different depending on the protocol version + // of the underlying chain. + // However, we can send batches with older protocol versions just by changing the encoding. + // This makes the migration simpler. + pub fn encode_for_eth_tx(&self, chain_protocol_version: ProtocolVersionId) -> Vec { + let internal_protocol_version = self.l1_batches[0].header.protocol_version.unwrap(); - if protocol_version.is_pre_gateway() { + if internal_protocol_version.is_pre_gateway() && chain_protocol_version.is_pre_gateway() { vec![Token::Array( self.l1_batches .iter() diff --git a/core/node/eth_sender/src/eth_tx_aggregator.rs b/core/node/eth_sender/src/eth_tx_aggregator.rs index 8a829ed00faa..bc9d3266ec78 100644 --- a/core/node/eth_sender/src/eth_tx_aggregator.rs +++ b/core/node/eth_sender/src/eth_tx_aggregator.rs @@ -594,6 +594,7 @@ impl EthTxAggregator { stm_protocol_version_id, stm_validator_timelock_address, ), + chain_protocol_version_id, is_gateway, ) .await?; @@ -642,7 +643,11 @@ impl EthTxAggregator { .await; } - fn encode_aggregated_op(&self, op: &AggregatedOperation) -> TxData { + fn encode_aggregated_op( + &self, + op: &AggregatedOperation, + chain_protocol_version_id: ProtocolVersionId, + ) -> TxData { let mut args = vec![Token::Uint(self.rollup_chain_id.as_u64().into())]; let is_op_pre_gateway = op.protocol_version().is_pre_gateway(); @@ -686,8 +691,9 @@ impl EthTxAggregator { (calldata, None) } AggregatedOperation::Execute(op) => { - args.extend(op.into_tokens()); - let encoding_fn = if is_op_pre_gateway { + args.extend(op.encode_for_eth_tx(chain_protocol_version_id)); + let encoding_fn = if is_op_pre_gateway && chain_protocol_version_id.is_pre_gateway() + { &self.functions.post_shared_bridge_execute } else { &self.functions.post_gateway_execute @@ -743,6 +749,7 @@ impl EthTxAggregator { storage: &mut Connection<'_, Core>, aggregated_op: &AggregatedOperation, timelock_contract_address: Address, + chain_protocol_version_id: ProtocolVersionId, is_gateway: bool, ) -> Result { let mut transaction = storage.start_transaction().await.unwrap(); @@ -755,7 +762,8 @@ impl EthTxAggregator { (_, _) => None, }; let nonce = self.get_next_nonce(&mut transaction, sender_addr).await?; - let encoded_aggregated_op = self.encode_aggregated_op(aggregated_op); + let encoded_aggregated_op = + self.encode_aggregated_op(aggregated_op, chain_protocol_version_id); let l1_batch_number_range = aggregated_op.l1_batch_range(); let eth_tx_predicted_gas = match (op_type, is_gateway, self.aggregator.mode()) { diff --git a/core/node/eth_sender/src/tester.rs b/core/node/eth_sender/src/tester.rs index 943e808cfa6b..022e2bc87222 100644 --- a/core/node/eth_sender/src/tester.rs +++ b/core/node/eth_sender/src/tester.rs @@ -13,7 +13,7 @@ use zksync_object_store::MockObjectStore; use zksync_types::{ aggregated_operations::AggregatedActionType, block::L1BatchHeader, commitment::L1BatchCommitmentMode, eth_sender::EthTx, pubdata_da::PubdataSendingMode, - settlement::SettlementMode, Address, L1BatchNumber, ProtocolVersion, H256, + settlement::SettlementMode, Address, L1BatchNumber, ProtocolVersion, ProtocolVersionId, H256, }; use crate::{ @@ -525,6 +525,7 @@ impl EthSenderTester { &mut self.conn.connection().await.unwrap(), &aggregated_operation, Address::random(), + ProtocolVersionId::latest(), self.is_l2, ) .await diff --git a/core/node/eth_sender/src/tests.rs b/core/node/eth_sender/src/tests.rs index f104d222982a..8841f297cadc 100644 --- a/core/node/eth_sender/src/tests.rs +++ b/core/node/eth_sender/src/tests.rs @@ -236,6 +236,7 @@ async fn resend_each_block(commitment_mode: L1BatchCommitmentMode) -> anyhow::Re &mut tester.conn.connection().await.unwrap(), &get_dummy_operation(0), Address::random(), + ProtocolVersionId::latest(), false, ) .await?; From fe3c7b2583bc4f9277e186334e5822ddf95bdcd0 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:32:12 +0200 Subject: [PATCH 11/52] feat: update l2 erc20 bridge address in updater as well (#3500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ update l2 erc20 bridge address as well as l2 shared bridge ## 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`. Co-authored-by: Jobe Smith <163019853+jobesmith-man@users.noreply.github.com> --- core/node/api_server/src/web3/state.rs | 3 ++- .../implementations/layers/web3_api/server/bridge_addresses.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/node/api_server/src/web3/state.rs b/core/node/api_server/src/web3/state.rs index 97ffd933c801..1319c866968c 100644 --- a/core/node/api_server/src/web3/state.rs +++ b/core/node/api_server/src/web3/state.rs @@ -241,8 +241,9 @@ impl BridgeAddressesHandle { self.0.write().await.l1_shared_default_bridge = Some(l1_shared_bridge); } - pub async fn update_l2_shared_bridge(&self, l2_shared_bridge: Address) { + pub async fn update_l2_bridges(&self, l2_shared_bridge: Address) { self.0.write().await.l2_shared_default_bridge = Some(l2_shared_bridge); + self.0.write().await.l2_erc20_default_bridge = Some(l2_shared_bridge); } pub async fn read(&self) -> api::BridgeAddresses { diff --git a/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs b/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs index a515e4cc1db9..b85d74699857 100644 --- a/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs +++ b/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs @@ -80,7 +80,7 @@ impl L1UpdaterInner { // - To not undo the previous change in case of a network error if info.should_use_l2_asset_router { self.bridge_address_updater - .update_l2_shared_bridge(L2_ASSET_ROUTER_ADDRESS) + .update_l2_bridges(L2_ASSET_ROUTER_ADDRESS) .await; } } From d9e911cfa6a1d4fdd49266c29918a61423210727 Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Mon, 20 Jan 2025 09:40:32 +0000 Subject: [PATCH 12/52] chore: fix zkstack completion for zsh (#3495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Fixes `zkstack` completion for `zsh` shell. ## Why ❔ For `zsh` shell default completion will fail with the following error: `command not found: compdef` because autocompletion is not activated by default. This PR adds the following to activate it on `zsh` in addition to `source` of the autocompletion script: ``` autoload -Uz compinit compinit ``` *[Related thread](https://unix.stackexchange.com/questions/339954/zsh-command-not-found-compinstall-compinit-compdef) ## 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`. --- zkstack_cli/crates/zkstack/build.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/zkstack_cli/crates/zkstack/build.rs b/zkstack_cli/crates/zkstack/build.rs index e52e952bf730..d2d478f80227 100644 --- a/zkstack_cli/crates/zkstack/build.rs +++ b/zkstack_cli/crates/zkstack/build.rs @@ -112,15 +112,22 @@ impl ShellAutocomplete for clap_complete::Shell { .context(format!("could not read .{}rc", shell))?; if !shell_rc_content.contains("# zkstack completion") { - std::fs::write( - shell_rc, + let completion_snippet = if shell == "zsh" { + format!( + "{}\n# zkstack completion\nautoload -Uz compinit\ncompinit\nsource \"{}\"\n", + shell_rc_content, + completion_file.to_str().unwrap() + ) + } else { format!( "{}\n# zkstack completion\nsource \"{}\"\n", shell_rc_content, completion_file.to_str().unwrap() - ), - ) - .context(format!("could not write .{}rc", shell))?; + ) + }; + + std::fs::write(shell_rc, completion_snippet) + .context(format!("could not write .{}rc", shell))?; } } else { println!( From 6a0c1a6650d8c65de77a63a92273e7499717b1af Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Mon, 20 Jan 2025 21:09:11 +1100 Subject: [PATCH 13/52] test: make fee tests robust (#3504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ A bunch of small improvements to make fee tests robust: * Upgrade ethers * Catch retryable networking issues in `ethers.FetchRequest` * Destroy providers on context teardown to avoid econnreset * Retry fee-related issues that can arise when transaction is rejected by mempool ## Why ❔ Fee tests are disruptively flaky right now ## 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/tests/recovery-test/package.json | 2 +- core/tests/revert-test/package.json | 2 +- core/tests/ts-integration/package.json | 2 +- .../tests/ts-integration/src/context-owner.ts | 18 +++++- core/tests/ts-integration/src/helpers.ts | 35 +++++++--- .../ts-integration/src/retry-provider.ts | 64 +++++++++++++------ core/tests/ts-integration/tests/fees.test.ts | 6 +- core/tests/upgrade-test/package.json | 2 +- yarn.lock | 45 +++++++------ 9 files changed, 124 insertions(+), 52 deletions(-) diff --git a/core/tests/recovery-test/package.json b/core/tests/recovery-test/package.json index 8b2ea7f054c0..28c469fc725d 100644 --- a/core/tests/recovery-test/package.json +++ b/core/tests/recovery-test/package.json @@ -23,7 +23,7 @@ "@types/node": "^18.19.15", "@types/node-fetch": "^2.5.7", "chai": "^4.3.4", - "ethers": "^6.7.1", + "ethers": "^6.13.5", "mocha": "^9.0.2", "mocha-steps": "^1.3.0", "node-fetch": "^2.6.1", diff --git a/core/tests/revert-test/package.json b/core/tests/revert-test/package.json index c3be63dff631..7dc2566eb0ef 100644 --- a/core/tests/revert-test/package.json +++ b/core/tests/revert-test/package.json @@ -24,7 +24,7 @@ "@types/node-fetch": "^2.5.7", "chai": "^4.3.4", "ethereumjs-abi": "^0.6.8", - "ethers": "^6.7.1", + "ethers": "^6.13.5", "mocha": "^9.0.2", "mocha-steps": "^1.3.0", "node-fetch": "^2.6.1", diff --git a/core/tests/ts-integration/package.json b/core/tests/ts-integration/package.json index 3362b9d6a89e..11d9c2d2e4ed 100644 --- a/core/tests/ts-integration/package.json +++ b/core/tests/ts-integration/package.json @@ -24,7 +24,7 @@ "chalk": "^4.0.0", "elliptic": "^6.5.5", "ethereumjs-abi": "^0.6.8", - "ethers": "^6.7.1", + "ethers": "^6.13.5", "hardhat": "=2.22.2", "jest": "^29.0.3", "jest-environment-node": "^29.0.3", diff --git a/core/tests/ts-integration/src/context-owner.ts b/core/tests/ts-integration/src/context-owner.ts index 57ca54da7b27..72d977cda6db 100644 --- a/core/tests/ts-integration/src/context-owner.ts +++ b/core/tests/ts-integration/src/context-owner.ts @@ -612,9 +612,25 @@ export class TestContextOwner { await this.waitForVmPlayground(); this.reporter.finishAction(); } - this.reporter.startAction(`Tearing down the context`); + this.reporter.startAction(`Collecting funds`); await this.collectFunds(); this.reporter.finishAction(); + this.reporter.startAction(`Destroying providers`); + // Destroy providers so that they drop potentially active connections to the node. Not doing so might cause + // unexpected network errors to propagate during node termination. + try { + this.l1Provider.destroy(); + } catch (err: any) { + // Catch any request cancellation errors that propagate here after destroying L1 provider + console.log(`Caught error while destroying L1 provider: ${err}`); + } + try { + this.l2Provider.destroy(); + } catch (err: any) { + // Catch any request cancellation errors that propagate here after destroying L2 provider + console.log(`Caught error while destroying L2 provider: ${err}`); + } + this.reporter.finishAction(); } catch (error: any) { // Report the issue to the console and mark the last action as failed. this.reporter.error(`An error occurred: ${error.message || error}`); diff --git a/core/tests/ts-integration/src/helpers.ts b/core/tests/ts-integration/src/helpers.ts index 88819c669655..758884e2be99 100644 --- a/core/tests/ts-integration/src/helpers.ts +++ b/core/tests/ts-integration/src/helpers.ts @@ -3,7 +3,6 @@ import * as zksync from 'zksync-ethers'; import * as ethers from 'ethers'; import * as hre from 'hardhat'; import { ZkSyncArtifact } from '@matterlabs/hardhat-zksync-solc/dist/src/types'; -import { TransactionReceipt } from 'ethers'; export const SYSTEM_CONTEXT_ADDRESS = '0x000000000000000000000000000000000000800b'; @@ -78,8 +77,8 @@ export async function anyTransaction(wallet: zksync.Wallet): Promise { const MAX_ATTEMPTS = 3; - let txResponse = null; - let txReceipt: TransactionReceipt | null = null; + let txResponse: ethers.TransactionResponse | null = null; + let txReceipt: ethers.TransactionReceipt | null = null; let nonce = Number(await wallet.getNonce()); for (let i = 0; i < MAX_ATTEMPTS; i++) { // Send a dummy transaction and wait for it to execute. We override `maxFeePerGas` as the default ethers behavior @@ -87,11 +86,31 @@ export async function waitForNewL1Batch(wallet: zksync.Wallet): Promise { + // Unlike `waitForTransaction` below, these errors are not wrapped as `EthersError` for some reason + if (e.message.match(/Not enough gas/)) { + console.log( + `Transaction did not have enough gas, likely gas price went up (attempt ${i + 1}/${MAX_ATTEMPTS})` + ); + return null; + } else if (e.message.match(/max fee per gas less than block base fee/)) { + console.log( + `Transaction's max fee per gas was lower than block base fee, likely gas price went up (attempt ${i + 1}/${MAX_ATTEMPTS})` + ); + return null; + } else { + return Promise.reject(e); + } + }); + if (!txResponse) { + continue; + } } else { console.log('Gas price has not gone up, waiting longer'); } diff --git a/core/tests/ts-integration/src/retry-provider.ts b/core/tests/ts-integration/src/retry-provider.ts index 4c89e0407b9e..9cf63da0638c 100644 --- a/core/tests/ts-integration/src/retry-provider.ts +++ b/core/tests/ts-integration/src/retry-provider.ts @@ -4,6 +4,22 @@ import { Reporter } from './reporter'; import { AugmentedTransactionResponse } from './transaction-response'; import { L1Provider, RetryableL1Wallet } from './l1-provider'; +// Error markers observed on stage so far. +const IGNORED_ERRORS = [ + 'timeout', + 'etimedout', + 'econnrefused', + 'econnreset', + 'bad gateway', + 'service temporarily unavailable', + 'nonetwork' +]; + +function isIgnored(err: any): boolean { + const errString: string = err.toString().toLowerCase(); + return IGNORED_ERRORS.some((sampleErr) => errString.indexOf(sampleErr) !== -1); +} + /** * RetryProvider retries every RPC request if it detects a timeout-related issue on the server side. */ @@ -11,17 +27,39 @@ export class RetryProvider extends zksync.Provider { private readonly reporter: Reporter; private readonly knownTransactionHashes: Set = new Set(); - constructor(_url?: string | { url: string; timeout: number }, network?: ethers.Networkish, reporter?: Reporter) { - let url; + constructor(_url: string | { url: string; timeout: number }, network?: ethers.Networkish, reporter?: Reporter) { + let fetchRequest: ethers.FetchRequest; if (typeof _url === 'object') { - const fetchRequest: ethers.FetchRequest = new ethers.FetchRequest(_url.url); + fetchRequest = new ethers.FetchRequest(_url.url); fetchRequest.timeout = _url.timeout; - url = fetchRequest; } else { - url = _url; + fetchRequest = new ethers.FetchRequest(_url); } + let defaultGetUrlFunc = ethers.FetchRequest.createGetUrlFunc(); + fetchRequest.getUrlFunc = async (req: ethers.FetchRequest, signal?: ethers.FetchCancelSignal) => { + // Retry network requests that failed because of temporary issues (such as timeout, econnreset). + for (let retry = 0; retry < 50; retry++) { + try { + const result = await defaultGetUrlFunc(req, signal); + // If we obtained result not from the first attempt, print a warning. + if (retry != 0) { + this.reporter?.debug(`RPC request ${req} took ${retry} retries to succeed`); + } + return result; + } catch (err: any) { + if (isIgnored(err)) { + // Error is related to timeouts. Sleep a bit and try again. + await zksync.utils.sleep(this.pollingInterval); + continue; + } + // Re-throw any non-timeout-related error. + throw err; + } + } + return Promise.reject(new Error(`Retried too many times, giving up on request=${req}`)); + }; - super(url, network); + super(fetchRequest, network); this.reporter = reporter ?? new Reporter(); } @@ -35,19 +73,7 @@ export class RetryProvider extends zksync.Provider { } return result; } catch (err: any) { - // Error markers observed on stage so far. - const ignoredErrors = [ - 'timeout', - 'etimedout', - 'econnrefused', - 'econnreset', - 'bad gateway', - 'service temporarily unavailable', - 'nonetwork' - ]; - const errString: string = err.toString().toLowerCase(); - const found = ignoredErrors.some((sampleErr) => errString.indexOf(sampleErr) !== -1); - if (found) { + if (isIgnored(err)) { // Error is related to timeouts. Sleep a bit and try again. await zksync.utils.sleep(this.pollingInterval); continue; diff --git a/core/tests/ts-integration/tests/fees.test.ts b/core/tests/ts-integration/tests/fees.test.ts index a111f5804852..f598cf49ce02 100644 --- a/core/tests/ts-integration/tests/fees.test.ts +++ b/core/tests/ts-integration/tests/fees.test.ts @@ -410,9 +410,13 @@ testFees('Test fees', function () { }); afterAll(async () => { - await mainNodeSpawner.killAndSpawnMainNode(); // Returning the pubdata price to the default one // Spawning with no options restores defaults. + await mainNodeSpawner.killAndSpawnMainNode(); + + // Wait for current batch to close so gas price returns to normal. + await waitForNewL1Batch(alice); + await testMaster.deinitialize(); __ZKSYNC_TEST_CONTEXT_OWNER__.setL2NodePid(mainNodeSpawner.mainNode!.proc.pid!); }); diff --git a/core/tests/upgrade-test/package.json b/core/tests/upgrade-test/package.json index 5bb23c36d3b8..57aefb7b7caa 100644 --- a/core/tests/upgrade-test/package.json +++ b/core/tests/upgrade-test/package.json @@ -23,7 +23,7 @@ "@types/node-fetch": "^2.5.7", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", - "ethers": "^6.7.1", + "ethers": "^6.13.5", "mocha": "^9.0.2", "mocha-steps": "^1.3.0", "node-fetch": "^2.6.1", diff --git a/yarn.lock b/yarn.lock index 732577daeb68..eabdce369080 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2943,10 +2943,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== -"@types/node@18.15.13": - version "18.15.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" - integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" "@types/node@^10.0.3": version "10.17.60" @@ -5487,18 +5489,18 @@ ethers@^5.0.2, ethers@^5.7.0, ethers@^5.7.2, ethers@~5.7.0, ethers@~5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" -ethers@^6.7.1: - version "6.12.1" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.12.1.tgz#517ff6d66d4fd5433e38e903051da3e57c87ff37" - integrity sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw== +ethers@^6.13.5: + version "6.13.5" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.5.tgz#8c1d6ac988ac08abc3c1d8fabbd4b8b602851ac4" + integrity sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ== dependencies: "@adraffy/ens-normalize" "1.10.1" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" - "@types/node" "18.15.13" + "@types/node" "22.7.5" aes-js "4.0.0-beta.5" - tslib "2.4.0" - ws "8.5.0" + tslib "2.7.0" + ws "8.17.1" ethers@~5.5.0: version "5.5.4" @@ -10791,10 +10793,10 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" @@ -11007,6 +11009,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici@^5.14.0: version "5.28.4" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" @@ -11287,10 +11294,10 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== ws@^7.4.6: version "7.5.9" From 731b8240abd4c0cfa42f2ce89c23f8ebf67e1bf2 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 20 Jan 2025 11:06:34 +0000 Subject: [PATCH 14/52] feat(vm): Implement call tracing for fast VM (#2905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ - Implements call tracing for the fast VM. - Enables call tracing for the batch VM executor (i.e., one used in the state keeper) if it is requested. Compares produced traces in the shadow VM mode. ## Why ❔ Call tracing is the last large missing part of the fast VM. --------- Co-authored-by: Alex Ostrovski --- core/lib/multivm/Cargo.toml | 2 +- .../multivm/src/tracers/call_tracer/mod.rs | 3 + .../src/tracers/call_tracer/vm_latest/mod.rs | 7 +- core/lib/multivm/src/versions/shadow/mod.rs | 2 +- core/lib/multivm/src/versions/shadow/tests.rs | 88 +++++- .../src/versions/testonly/call_tracer.rs | 292 ++++++++++++++++++ core/lib/multivm/src/versions/testonly/mod.rs | 13 +- .../src/versions/testonly/tester/mod.rs | 5 + core/lib/multivm/src/versions/vm_fast/mod.rs | 2 +- .../src/versions/vm_fast/tests/call_tracer.rs | 31 ++ .../multivm/src/versions/vm_fast/tests/mod.rs | 19 +- .../src/versions/vm_fast/tracers/calls.rs | 155 ++++++++++ .../versions/vm_fast/tracers/evm_deploy.rs | 4 +- .../src/versions/vm_fast/tracers/mod.rs | 6 +- .../versions/vm_fast/tracers/validation.rs | 8 +- .../lib/multivm/src/versions/vm_fast/utils.rs | 7 +- .../versions/vm_latest/tests/call_tracer.rs | 61 ++-- .../src/versions/vm_latest/tests/mod.rs | 16 +- core/lib/vm_executor/src/batch/factory.rs | 54 +++- core/lib/vm_interface/src/utils/shadow.rs | 72 ++++- .../api_server/src/web3/namespaces/debug.rs | 1 + .../state_keeper/src/executor/tests/mod.rs | 49 ++- .../state_keeper/src/executor/tests/tester.rs | 62 +--- core/node/state_keeper/src/testonly/mod.rs | 88 ++---- 24 files changed, 857 insertions(+), 190 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/call_tracer.rs create mode 100644 core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs create mode 100644 core/lib/multivm/src/versions/vm_fast/tracers/calls.rs diff --git a/core/lib/multivm/Cargo.toml b/core/lib/multivm/Cargo.toml index 107a168e305a..325a6f3a99e3 100644 --- a/core/lib/multivm/Cargo.toml +++ b/core/lib/multivm/Cargo.toml @@ -40,5 +40,5 @@ assert_matches.workspace = true pretty_assertions.workspace = true rand.workspace = true test-casing.workspace = true -zksync_test_contracts.workspace = true zksync_eth_signer.workspace = true +zksync_test_contracts.workspace = true diff --git a/core/lib/multivm/src/tracers/call_tracer/mod.rs b/core/lib/multivm/src/tracers/call_tracer/mod.rs index 44f274876032..20beb10642d8 100644 --- a/core/lib/multivm/src/tracers/call_tracer/mod.rs +++ b/core/lib/multivm/src/tracers/call_tracer/mod.rs @@ -17,6 +17,8 @@ pub mod vm_virtual_blocks; #[derive(Debug, Clone)] pub struct CallTracer { stack: Vec, + finished_calls: Vec, + result: Arc>>, max_stack_depth: usize, @@ -41,6 +43,7 @@ impl CallTracer { pub fn new(result: Arc>>) -> Self { Self { stack: vec![], + finished_calls: vec![], result, max_stack_depth: 0, max_near_calls: 0, diff --git a/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs b/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs index ed18a3eca47d..afa02d24f200 100644 --- a/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs +++ b/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs @@ -67,7 +67,9 @@ impl VmTracer for CallTracer { _bootloader_state: &BootloaderState, _stop_reason: VmExecutionStopReason, ) { - self.store_result() + let result = std::mem::take(&mut self.finished_calls); + let cell = self.result.as_ref(); + cell.set(result).unwrap(); } } @@ -191,7 +193,6 @@ impl CallTracer { .farcall .parent_gas .saturating_sub(state.vm_local_state.callstack.current.ergs_remaining as u64); - self.save_output_latest(state, memory, ret_opcode, &mut current_call.farcall); // If there is a parent call, push the current call to it @@ -199,7 +200,7 @@ impl CallTracer { if let Some(parent_call) = self.stack.last_mut() { parent_call.farcall.calls.push(current_call.farcall); } else { - self.push_call_and_update_stats(current_call.farcall, current_call.near_calls_after); + self.finished_calls.push(current_call.farcall); } } } diff --git a/core/lib/multivm/src/versions/shadow/mod.rs b/core/lib/multivm/src/versions/shadow/mod.rs index 1ad5bdba5a7b..ee1eb02b0dd5 100644 --- a/core/lib/multivm/src/versions/shadow/mod.rs +++ b/core/lib/multivm/src/versions/shadow/mod.rs @@ -25,7 +25,7 @@ use crate::{ mod tests; type ReferenceVm = vm_latest::Vm, HistoryEnabled>; -type ShadowedFastVm = crate::vm_instance::ShadowedFastVm; +type ShadowedFastVm = crate::vm_instance::ShadowedFastVm; fn hash_block(block_env: L2BlockEnv, tx_hashes: &[H256]) -> H256 { let mut hasher = L2BlockHasher::new( diff --git a/core/lib/multivm/src/versions/shadow/tests.rs b/core/lib/multivm/src/versions/shadow/tests.rs index 2d3dd5d3ae30..dc7417ad1259 100644 --- a/core/lib/multivm/src/versions/shadow/tests.rs +++ b/core/lib/multivm/src/versions/shadow/tests.rs @@ -1,20 +1,30 @@ //! Unit tests from the `testonly` test suite. -use std::{collections::HashSet, rc::Rc}; +use std::{collections::HashSet, fmt, rc::Rc}; use zksync_types::{writes::StateDiffRecord, StorageKey, Transaction, H256, U256}; +use zksync_vm2::interface::Tracer; +use zksync_vm_interface::{ + utils::{CheckDivergence, DivergenceErrors}, + Call, +}; use super::ShadowedFastVm; use crate::{ interface::{ pubdata::{PubdataBuilder, PubdataInput}, + storage::InMemoryStorage, utils::{ShadowMut, ShadowRef}, CurrentExecutionState, L2BlockEnv, VmExecutionResultAndLogs, }, - versions::testonly::TestedVm, + versions::testonly::{TestedVm, TestedVmWithCallTracer}, + vm_fast, }; -impl TestedVm for ShadowedFastVm { +impl TestedVm for ShadowedFastVm +where + Tr: Tracer + Default + fmt::Debug + 'static, +{ type StateDump = (); fn dump_state(&self) -> Self::StateDump { @@ -135,6 +145,44 @@ impl TestedVm for ShadowedFastVm { } } +#[derive(Debug)] +struct ExecutionResultAndTraces { + result: VmExecutionResultAndLogs, + traces: Vec, +} + +impl From<(VmExecutionResultAndLogs, Vec)> for ExecutionResultAndTraces { + fn from((result, traces): (VmExecutionResultAndLogs, Vec)) -> Self { + Self { result, traces } + } +} + +impl From for (VmExecutionResultAndLogs, Vec) { + fn from(value: ExecutionResultAndTraces) -> Self { + (value.result, value.traces) + } +} + +impl CheckDivergence for ExecutionResultAndTraces { + fn check_divergence(&self, other: &Self) -> DivergenceErrors { + let mut errors = self.result.check_divergence(&other.result); + errors.extend(self.traces.check_divergence(&other.traces)); + errors + } +} + +impl TestedVmWithCallTracer for ShadowedFastVm { + fn inspect_with_call_tracer(&mut self) -> (VmExecutionResultAndLogs, Vec) { + self.get_custom_mut("inspect_with_call_tracer", |r| { + ExecutionResultAndTraces::from(match r { + ShadowMut::Main(vm) => vm.inspect_with_call_tracer(), + ShadowMut::Shadow(vm) => vm.inspect_with_call_tracer(), + }) + }) + .into() + } +} + mod block_tip { use crate::versions::testonly::block_tip::*; @@ -167,6 +215,40 @@ mod bytecode_publishing { } } +mod call_tracer { + use crate::versions::testonly::call_tracer::*; + + #[test] + fn basic_behavior() { + test_basic_behavior::>(); + } + + #[test] + fn transfer() { + test_transfer::>(); + } + + #[test] + fn reverted_tx() { + test_reverted_tx::>(); + } + + #[test] + fn reverted_deployment() { + test_reverted_deployment_tx::>(); + } + + #[test] + fn out_of_gas() { + test_out_of_gas::>(); + } + + #[test] + fn recursive_tx() { + test_recursive_tx::>(); + } +} + mod circuits { use crate::versions::testonly::circuits::*; diff --git a/core/lib/multivm/src/versions/testonly/call_tracer.rs b/core/lib/multivm/src/versions/testonly/call_tracer.rs new file mode 100644 index 000000000000..0e86cebec46e --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/call_tracer.rs @@ -0,0 +1,292 @@ +//! Call tracer tests. These tests are special in the sense that it's too unreliable to keep fixtures +//! (since they can be invalidated by unrelated changes in system contracts, e.g. by changing consumed gas costs). + +use assert_matches::assert_matches; +use ethabi::Token; +use zksync_system_constants::MSG_VALUE_SIMULATOR_ADDRESS; +use zksync_test_contracts::{Account, LoadnextContractExecutionParams, TestContract, TxType}; +use zksync_types::{ + fee::Fee, utils::deployed_address_create, zk_evm_types::FarCallOpcode, Address, Execute, +}; + +use super::{ContractToDeploy, TestedVmWithCallTracer, VmTester, VmTesterBuilder}; +use crate::{ + interface::{Call, CallType, ExecutionResult, TxExecutionMode}, + vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, +}; + +fn check_call(call: &Call) { + assert!(call.gas_used < call.gas); + assert!(call.gas_used > call.calls.iter().map(|call| call.gas_used).sum::()); + + for subcall in &call.calls { + if subcall.r#type != CallType::Call(FarCallOpcode::Mimic) { + pretty_assertions::assert_eq!(call.to, subcall.from); + } + check_call(subcall); + } +} + +fn extract_single_call(calls: &[Call], filter: impl Fn(&Call) -> bool) -> &Call { + fn walk<'a>( + matching_call: &mut Option<&'a Call>, + calls: &'a [Call], + filter: &impl Fn(&Call) -> bool, + ) { + for call in calls { + if filter(call) { + if let Some(prev_call) = matching_call { + panic!("Multiple call match filter: {prev_call:?}, {call:?}"); + } + *matching_call = Some(call); + } + walk(matching_call, &call.calls, filter); + } + } + + let mut matching_call = None; + walk(&mut matching_call, calls, &filter); + matching_call.expect("no calls match the filter") +} + +pub(crate) fn test_basic_behavior() { + let bytecode = TestContract::counter().bytecode.to_vec(); + let address = Address::repeat_byte(0xA5); + let mut vm: VmTester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![ContractToDeploy::new(bytecode, address)]) + .build(); + + let calldata = "7cf5dab00000000000000000000000000000000000000000000000000000000000000006"; + let calldata = hex::decode(calldata).unwrap(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(address), + calldata: calldata.clone(), + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + + vm.vm.push_transaction(tx); + let (res, call_traces) = vm.vm.inspect_with_call_tracer(); + assert!(!res.result.is_failed(), "{:#?}", res.result); + + for call in &call_traces { + check_call(call); + assert_eq!(call.error, None); + assert_eq!(call.revert_reason, None); + } + + let call_to_contract = extract_single_call(&call_traces, |call| call.to == address); + assert_eq!(call_to_contract.from, account.address); + assert_eq!(call_to_contract.input, calldata); +} + +pub(crate) fn test_transfer() { + let mut vm: VmTester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build(); + + let recipient = Address::repeat_byte(0x23); + let value = 1_000_000_000.into(); + let account = &mut vm.rich_accounts[0]; + let transfer = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(recipient), + calldata: vec![], + value, + factory_deps: vec![], + }, + None, + ); + + vm.vm.push_transaction(transfer); + let (res, call_traces) = vm.vm.inspect_with_call_tracer(); + assert!(!res.result.is_failed(), "{:#?}", res.result); + + for call in &call_traces { + check_call(call); + assert_eq!(call.error, None); + assert_eq!(call.revert_reason, None); + } + + let transfer_call = extract_single_call(&call_traces, |call| call.to == recipient); + assert_eq!(transfer_call.from, account.address); + assert_eq!(transfer_call.value, value); +} + +pub(crate) fn test_reverted_tx() { + let counter_address = Address::repeat_byte(0x23); + let mut vm: VmTester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![ContractToDeploy::new( + TestContract::counter().bytecode.to_vec(), + counter_address, + )]) + .build(); + + let account = &mut vm.rich_accounts[0]; + let calldata = TestContract::counter() + .function("incrementWithRevert") + .encode_input(&[Token::Uint(1.into()), Token::Bool(true)]) + .unwrap(); + let reverted_tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(counter_address), + calldata, + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + + vm.vm.push_transaction(reverted_tx); + let (res, call_traces) = vm.vm.inspect_with_call_tracer(); + assert_matches!(&res.result, ExecutionResult::Revert { .. }); + + let call_to_contract = extract_single_call(&call_traces, |call| call.to == counter_address); + assert_eq!( + call_to_contract.revert_reason.as_ref().unwrap(), + "This method always reverts" + ); +} + +pub(crate) fn test_out_of_gas() { + let contract_address = Address::repeat_byte(0x23); + let mut vm: VmTester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![ContractToDeploy::new( + TestContract::expensive().bytecode.to_vec(), + contract_address, + )]) + .build(); + + let account = &mut vm.rich_accounts[0]; + let execute = Execute { + contract_address: Some(contract_address), + calldata: TestContract::expensive() + .function("expensive") + .encode_input(&[Token::Uint(1_000.into())]) + .unwrap(), + value: 0.into(), + factory_deps: vec![], + }; + let out_of_gas_tx = account.get_l2_tx_for_execute( + execute, + Some(Fee { + gas_limit: 500_000.into(), // insufficient gas + ..Account::default_fee() + }), + ); + + vm.vm.push_transaction(out_of_gas_tx); + let (res, call_traces) = vm.vm.inspect_with_call_tracer(); + assert_matches!(&res.result, ExecutionResult::Revert { .. }); + + let out_of_gas_call = extract_single_call(&call_traces, |call| { + call.from == account.address && call.to == contract_address + }); + assert_eq!(out_of_gas_call.error.as_ref().unwrap(), "Panic"); + assert_eq!(out_of_gas_call.gas_used, out_of_gas_call.gas); + + let parent_call = + extract_single_call(&call_traces, |call| call.calls.contains(out_of_gas_call)); + assert_eq!( + parent_call.revert_reason.as_ref().unwrap(), + "Unknown revert reason" + ); +} + +pub(crate) fn test_reverted_deployment_tx() { + let mut vm: VmTester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build(); + + let account = &mut vm.rich_accounts[0]; + let deploy_tx = account.get_deploy_tx(TestContract::failed_call().bytecode, None, TxType::L2); + + vm.vm.push_transaction(deploy_tx.tx); + let (res, call_traces) = vm.vm.inspect_with_call_tracer(); + assert_matches!(&res.result, ExecutionResult::Success { .. }); + + let constructor_call = extract_single_call(&call_traces, |call| { + call.r#type == CallType::Create && call.from == account.address + }); + assert_eq!(constructor_call.input, [] as [u8; 0]); + assert_eq!(constructor_call.error, None); + assert_eq!(constructor_call.revert_reason, None); + let deploy_address = deployed_address_create(account.address, 0.into()); + assert_eq!(constructor_call.to, deploy_address); + + assert_eq!(constructor_call.calls.len(), 1, "{constructor_call:#?}"); + let inner_call = &constructor_call.calls[0]; + assert_eq!(inner_call.from, deploy_address); + assert_eq!(inner_call.to, MSG_VALUE_SIMULATOR_ADDRESS); + inner_call.revert_reason.as_ref().unwrap(); +} + +pub(crate) fn test_recursive_tx() { + let contract_address = Address::repeat_byte(0x42); + let mut vm: VmTester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_rich_accounts(1) + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![ContractToDeploy::new( + TestContract::load_test().bytecode.to_vec(), + contract_address, + )]) + .build(); + + let account = &mut vm.rich_accounts[0]; + let calldata = LoadnextContractExecutionParams { + recursive_calls: 20, + ..LoadnextContractExecutionParams::empty() + } + .to_bytes(); + let recursive_tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(contract_address), + calldata: calldata.clone(), + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + + vm.vm.push_transaction(recursive_tx); + let (res, call_traces) = vm.vm.inspect_with_call_tracer(); + assert!(!res.result.is_failed(), "{:#?}", res.result); + + let mut call_to_contract = extract_single_call(&call_traces, |call| { + call.to == contract_address && call.input == calldata + }); + let mut depth = 0; + while let Some(child_call) = call_to_contract.calls.first() { + assert_eq!(call_to_contract.calls.len(), 1, "{call_to_contract:#?}"); + assert_eq!(child_call.from, contract_address); + assert_eq!(child_call.to, contract_address); + assert_ne!(child_call.input, call_to_contract.input); + + depth += 1; + call_to_contract = child_call; + } + assert_eq!(depth, 20); +} diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 8f24c849272d..c1a603bfeefc 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -20,15 +20,17 @@ use zksync_types::{ get_is_account_key, h256_to_u256, u256_to_h256, utils::storage_key_for_eth_balance, Address, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, U256, }; -use zksync_vm_interface::{ - pubdata::PubdataBuilder, L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode, -}; pub(super) use self::tester::{ - validation_params, TestedVm, TestedVmForValidation, VmTester, VmTesterBuilder, + validation_params, TestedVm, TestedVmForValidation, TestedVmWithCallTracer, VmTester, + VmTesterBuilder, }; use crate::{ - interface::storage::InMemoryStorage, pubdata_builders::FullPubdataBuilder, + interface::{ + pubdata::PubdataBuilder, storage::InMemoryStorage, L1BatchEnv, L2BlockEnv, SystemEnv, + TxExecutionMode, + }, + pubdata_builders::FullPubdataBuilder, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; @@ -36,6 +38,7 @@ pub(super) mod account_validation_rules; pub(super) mod block_tip; pub(super) mod bootloader; pub(super) mod bytecode_publishing; +pub(super) mod call_tracer; pub(super) mod circuits; pub(super) mod code_oracle; pub(super) mod default_aa; diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index c29f2dbbf8f3..a25909525f11 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -8,6 +8,7 @@ use zksync_types::{ writes::StateDiffRecord, Address, L1BatchNumber, StorageKey, Transaction, H256, U256, }; +use zksync_vm_interface::Call; pub(crate) use self::transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier}; use super::get_empty_storage; @@ -252,3 +253,7 @@ pub(crate) fn validation_params(tx: &L2Tx, system: &SystemEnv) -> ValidationPara timestamp_asserter_params: None, } } + +pub(crate) trait TestedVmWithCallTracer: TestedVm { + fn inspect_with_call_tracer(&mut self) -> (VmExecutionResultAndLogs, Vec); +} diff --git a/core/lib/multivm/src/versions/vm_fast/mod.rs b/core/lib/multivm/src/versions/vm_fast/mod.rs index 58e7ad4b006c..291961d3312a 100644 --- a/core/lib/multivm/src/versions/vm_fast/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/mod.rs @@ -2,7 +2,7 @@ pub use zksync_vm2::interface; pub(crate) use self::version::FastVmVersion; pub use self::{ - tracers::{FullValidationTracer, ValidationTracer}, + tracers::{CallTracer, FullValidationTracer, ValidationTracer}, vm::Vm, }; diff --git a/core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs new file mode 100644 index 000000000000..9a990bb8d200 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs @@ -0,0 +1,31 @@ +use crate::versions::{testonly::call_tracer, vm_fast::Vm}; + +#[test] +fn basic_behavior() { + call_tracer::test_basic_behavior::>(); +} + +#[test] +fn transfer() { + call_tracer::test_transfer::>(); +} + +#[test] +fn reverted_tx() { + call_tracer::test_reverted_tx::>(); +} + +#[test] +fn reverted_deployment() { + call_tracer::test_reverted_deployment_tx::>(); +} + +#[test] +fn out_of_gas() { + call_tracer::test_out_of_gas::>(); +} + +#[test] +fn recursive_tx() { + call_tracer::test_recursive_tx::>(); +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index e148444922ba..89edd85b86f1 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -8,21 +8,24 @@ use zksync_vm_interface::{ pubdata::{PubdataBuilder, PubdataInput}, storage::ReadStorage, tracer::ViolatedValidationRule, - CurrentExecutionState, InspectExecutionMode, L2BlockEnv, VmExecutionMode, + Call, CurrentExecutionState, InspectExecutionMode, L2BlockEnv, VmExecutionMode, VmExecutionResultAndLogs, VmInterface, }; use super::{FullValidationTracer, ValidationTracer, Vm}; use crate::{ interface::storage::{ImmutableStorageView, InMemoryStorage}, - versions::testonly::{validation_params, TestedVm, TestedVmForValidation}, - vm_fast::tracers::WithBuiltinTracers, + versions::testonly::{ + validation_params, TestedVm, TestedVmForValidation, TestedVmWithCallTracer, + }, + vm_fast::{tracers::WithBuiltinTracers, CallTracer}, }; mod account_validation_rules; mod block_tip; mod bootloader; mod bytecode_publishing; +mod call_tracer; mod circuits; mod code_oracle; mod default_aa; @@ -180,7 +183,7 @@ where } } -impl TestedVmForValidation for Vm, (), FullValidationTracer> { +impl TestedVmForValidation for TestedFastVm<(), FullValidationTracer> { fn run_validation(&mut self, tx: L2Tx, timestamp: u64) -> Option { let validation_params = validation_params(&tx, &self.system_env); self.push_transaction(tx.into()); @@ -189,3 +192,11 @@ impl TestedVmForValidation for Vm, (), Ful tracer.1.validation_error() } } + +impl TestedVmWithCallTracer for TestedFastVm { + fn inspect_with_call_tracer(&mut self) -> (VmExecutionResultAndLogs, Vec) { + let mut tracer = (CallTracer::default(), ()); + let result = self.inspect(&mut tracer, InspectExecutionMode::OneTx); + (result, tracer.0.into_result()) + } +} diff --git a/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs b/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs new file mode 100644 index 000000000000..727e33ebb8d2 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs @@ -0,0 +1,155 @@ +use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; +use zksync_types::{zk_evm_types::FarCallOpcode, U256}; +use zksync_vm2::{ + interface::{ + CallframeInterface, CallingMode, Opcode, OpcodeType, ReturnType, ShouldStop, + StateInterface, Tracer, + }, + FatPointer, +}; + +use crate::{ + interface::{Call, CallType, VmRevertReason}, + vm_fast::utils::read_raw_fat_pointer, +}; + +/// Call tracer for the fast VM. +#[derive(Debug, Clone, Default)] +pub struct CallTracer { + stack: Vec, + finished_calls: Vec, + current_stack_depth: usize, + // TODO: report as metrics + max_stack_depth: usize, + max_near_calls: usize, +} + +#[derive(Debug, Clone)] +struct FarcallAndNearCallCount { + farcall: Call, + near_calls_after: usize, +} + +impl CallTracer { + /// Converts this tracer into the captured calls. + pub fn into_result(self) -> Vec { + self.finished_calls + } +} + +impl Tracer for CallTracer { + fn after_instruction( + &mut self, + state: &mut S, + ) -> ShouldStop { + match OP::VALUE { + Opcode::FarCall(ty) => { + self.current_stack_depth += 1; + self.max_stack_depth = self.max_stack_depth.max(self.current_stack_depth); + + let current_gas = state.current_frame().gas() as u64; + let from = state.current_frame().caller(); + let to = state.current_frame().address(); + let input = read_raw_fat_pointer(state, state.read_register(1).0); + let value = U256::from(state.current_frame().context_u128()); + let ty = match ty { + CallingMode::Normal => CallType::Call(FarCallOpcode::Normal), + CallingMode::Delegate => CallType::Call(FarCallOpcode::Delegate), + CallingMode::Mimic => { + let prev_this_address = state.callframe(1).address(); + if prev_this_address == CONTRACT_DEPLOYER_ADDRESS { + // EraVM contract creation is encoded as a mimic call from `ContractDeployer` to the created contract. + CallType::Create + } else { + CallType::Call(FarCallOpcode::Mimic) + } + } + }; + + self.stack.push(FarcallAndNearCallCount { + farcall: Call { + r#type: ty, + from, + to, + // The previous frame always exists directly after a far call + parent_gas: current_gas + state.callframe(1).gas() as u64, + gas: current_gas, + input, + value, + ..Default::default() + }, + near_calls_after: 0, + }); + } + Opcode::NearCall => { + self.current_stack_depth += 1; + self.max_stack_depth = self.max_stack_depth.max(self.current_stack_depth); + + if let Some(frame) = self.stack.last_mut() { + frame.near_calls_after += 1; + self.max_near_calls = self.max_near_calls.max(frame.near_calls_after); + } + } + Opcode::Ret(variant) => { + self.current_stack_depth -= 1; + + let Some(mut current_call) = self.stack.pop() else { + return ShouldStop::Continue; + }; + + if current_call.near_calls_after == 0 { + // Might overflow due to stipend + current_call.farcall.gas_used = current_call + .farcall + .parent_gas + .saturating_sub(state.current_frame().gas() as u64); + + let (maybe_output_ptr, is_pointer) = state.read_register(1); + let output = if is_pointer { + let output_ptr = FatPointer::from(maybe_output_ptr); + if output_ptr.length == 0 && output_ptr.offset == 0 { + // Trivial pointer, which is formally cannot be dereferenced. This only matters + // when extracting the revert reason; the legacy VM treats the trivial pointer specially. + None + } else { + Some(read_raw_fat_pointer(state, maybe_output_ptr)) + } + } else { + None + }; + + match variant { + ReturnType::Normal => { + current_call.farcall.output = output.unwrap_or_default(); + } + ReturnType::Revert => { + current_call.farcall.revert_reason = + Some(if let Some(output) = &output { + VmRevertReason::from(output.as_slice()).to_string() + } else { + "Unknown revert reason".to_owned() + }); + } + ReturnType::Panic => { + current_call.farcall.error = Some("Panic".to_string()); + } + } + + // If there is a parent call, push the current call to it + // Otherwise, put the current call back on the stack, because it's the top level call + if let Some(parent_call) = self.stack.last_mut() { + parent_call.farcall.calls.push(current_call.farcall); + } else { + self.finished_calls.push(current_call.farcall); + } + } else { + current_call.near_calls_after -= 1; + self.stack.push(current_call); + } + } + _ => {} + } + + ShouldStop::Continue + } +} diff --git a/core/lib/multivm/src/versions/vm_fast/tracers/evm_deploy.rs b/core/lib/multivm/src/versions/vm_fast/tracers/evm_deploy.rs index 1202b5b94dd2..deb06366eb36 100644 --- a/core/lib/multivm/src/versions/vm_fast/tracers/evm_deploy.rs +++ b/core/lib/multivm/src/versions/vm_fast/tracers/evm_deploy.rs @@ -8,7 +8,7 @@ use zksync_vm2::interface::{ CallframeInterface, CallingMode, GlobalStateInterface, Opcode, OpcodeType, ShouldStop, Tracer, }; -use crate::vm_fast::utils::read_fat_pointer; +use crate::vm_fast::utils::read_raw_fat_pointer; /// Container for dynamic bytecodes added by [`EvmDeployTracer`]. #[derive(Debug, Clone, Default)] @@ -54,7 +54,7 @@ impl EvmDeployTracer { return; } - let data = read_fat_pointer(state, state.read_register(1).0); + let data = read_raw_fat_pointer(state, state.read_register(1).0); if data.len() < 4 { return; } diff --git a/core/lib/multivm/src/versions/vm_fast/tracers/mod.rs b/core/lib/multivm/src/versions/vm_fast/tracers/mod.rs index 3d9602536743..db527bdbaceb 100644 --- a/core/lib/multivm/src/versions/vm_fast/tracers/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tracers/mod.rs @@ -3,10 +3,14 @@ use zksync_vm2::interface::{CycleStats, GlobalStateInterface, OpcodeType, ShouldStop, Tracer}; pub(super) use self::evm_deploy::DynamicBytecodes; -pub use self::validation::{FullValidationTracer, ValidationTracer}; +pub use self::{ + calls::CallTracer, + validation::{FullValidationTracer, ValidationTracer}, +}; use self::{circuits::CircuitsTracer, evm_deploy::EvmDeployTracer}; use crate::interface::CircuitStatistic; +mod calls; mod circuits; mod evm_deploy; mod validation; diff --git a/core/lib/multivm/src/versions/vm_fast/tracers/validation.rs b/core/lib/multivm/src/versions/vm_fast/tracers/validation.rs index 52b0a4747b7d..6833051c5407 100644 --- a/core/lib/multivm/src/versions/vm_fast/tracers/validation.rs +++ b/core/lib/multivm/src/versions/vm_fast/tracers/validation.rs @@ -14,7 +14,7 @@ use zksync_vm_interface::tracer::{ TimestampAsserterParams, ValidationParams, ValidationTraces, ViolatedValidationRule, }; -use crate::{tracers::TIMESTAMP_ASSERTER_FUNCTION_SELECTOR, vm_fast::utils::read_fat_pointer}; +use crate::{tracers::TIMESTAMP_ASSERTER_FUNCTION_SELECTOR, vm_fast::utils::read_raw_fat_pointer}; /// [`Tracer`] used for account validation per [EIP-4337] and [EIP-7562]. /// @@ -157,7 +157,7 @@ impl Tracer for FullValidationTracer { // Intercept calls to keccak, whitelist storage slots corresponding to the hash let code_address = state.current_frame().code_address(); if code_address == KECCAK256_PRECOMPILE_ADDRESS { - let calldata = read_fat_pointer(state, state.read_register(1).0); + let calldata = read_raw_fat_pointer(state, state.read_register(1).0); if calldata.len() != 64 { return ShouldStop::Continue; } @@ -185,7 +185,7 @@ impl Tracer for FullValidationTracer { if let Some(ref params) = self.timestamp_asserter_params { if code_address == params.address { - let calldata = read_fat_pointer(state, state.read_register(1).0); + let calldata = read_raw_fat_pointer(state, state.read_register(1).0); if calldata.len() == 68 && calldata[..4] == TIMESTAMP_ASSERTER_FUNCTION_SELECTOR { @@ -215,7 +215,7 @@ impl Tracer for FullValidationTracer { } Ret(kind) => { if self.add_return_value_to_allowed_slots && kind == Normal { - let return_value = read_fat_pointer(state, state.read_register(1).0); + let return_value = read_raw_fat_pointer(state, state.read_register(1).0); self.slots_obtained_via_keccak .insert(return_value.as_slice().into()); } diff --git a/core/lib/multivm/src/versions/vm_fast/utils.rs b/core/lib/multivm/src/versions/vm_fast/utils.rs index 20a6545d3385..8b3f57be988d 100644 --- a/core/lib/multivm/src/versions/vm_fast/utils.rs +++ b/core/lib/multivm/src/versions/vm_fast/utils.rs @@ -1,8 +1,11 @@ use zksync_types::U256; use zksync_vm2::{interface::StateInterface, FatPointer}; -pub(super) fn read_fat_pointer(state: &S, raw: U256) -> Vec { - let pointer = FatPointer::from(raw); +pub(super) fn read_raw_fat_pointer(state: &S, raw: U256) -> Vec { + read_fat_pointer(state, FatPointer::from(raw)) +} + +pub(super) fn read_fat_pointer(state: &S, pointer: FatPointer) -> Vec { let length = pointer.length - pointer.offset; let start = pointer.start + pointer.offset; let mut result = vec![0; length as usize]; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs index c8f623478569..451647545671 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs @@ -1,15 +1,14 @@ use std::sync::Arc; use once_cell::sync::OnceCell; -use zksync_test_contracts::TestContract; use zksync_types::{Address, Execute}; use super::TestedLatestVm; use crate::{ interface::{InspectExecutionMode, TxExecutionMode, VmInterface}, tracers::CallTracer, - versions::testonly::{read_max_depth_contract, ContractToDeploy, VmTesterBuilder}, - vm_latest::{constants::BATCH_COMPUTATIONAL_GAS_LIMIT, ToTracerPointer}, + versions::testonly::{call_tracer, read_max_depth_contract, ContractToDeploy, VmTesterBuilder}, + vm_latest::{constants::BATCH_COMPUTATIONAL_GAS_LIMIT, ToTracerPointer, Vm}, }; // This test is ultra slow, so it's ignored by default. @@ -48,43 +47,31 @@ fn test_max_depth() { } #[test] -fn test_basic_behavior() { - let contract = TestContract::counter().bytecode.to_vec(); - let address = Address::repeat_byte(1); - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_rich_accounts(1) - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) - .build::(); +fn basic_behavior() { + call_tracer::test_basic_behavior::>(); +} - let increment_by_6_calldata = - "7cf5dab00000000000000000000000000000000000000000000000000000000000000006"; +#[test] +fn transfer() { + call_tracer::test_transfer::>(); +} - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: hex::decode(increment_by_6_calldata).unwrap(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); +#[test] +fn reverted_tx() { + call_tracer::test_reverted_tx::>(); +} - let result = Arc::new(OnceCell::new()); - let call_tracer = CallTracer::new(result.clone()).into_tracer_pointer(); - vm.vm.push_transaction(tx); - let res = vm - .vm - .inspect(&mut call_tracer.into(), InspectExecutionMode::OneTx); +#[test] +fn reverted_deployment() { + call_tracer::test_reverted_deployment_tx::>(); +} - let call_tracer_result = result.get().unwrap(); +#[test] +fn out_of_gas() { + call_tracer::test_out_of_gas::>(); +} - assert_eq!(call_tracer_result.len(), 1); - // Expect that there are a plenty of subcalls underneath. - let subcall = &call_tracer_result[0].calls; - assert!(subcall.len() > 10); - assert!(!res.result.is_failed()); +#[test] +fn recursive_tx() { + call_tracer::test_recursive_tx::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs index 10750f6ffaaf..260b1a4eeef3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -4,6 +4,7 @@ use std::{ sync::Arc, }; +use once_cell::sync::OnceCell; use zk_evm_1_5_0::{ aux_structures::{MemoryPage, Timestamp}, vm_state::VmLocalState, @@ -13,7 +14,7 @@ use zksync_types::{ bytecode::BytecodeHash, l2::L2Tx, vm::VmVersion, writes::StateDiffRecord, StorageKey, StorageValue, Transaction, H256, U256, }; -use zksync_vm_interface::VmInterface; +use zksync_vm_interface::{Call, InspectExecutionMode, VmInterface}; use super::{HistoryEnabled, ToTracerPointer, Vm}; use crate::{ @@ -23,10 +24,11 @@ use crate::{ tracer::ViolatedValidationRule, CurrentExecutionState, L2BlockEnv, VmExecutionMode, VmExecutionResultAndLogs, }, - tracers::ValidationTracer, + tracers::{CallTracer, ValidationTracer}, utils::bytecode::bytes_to_be_words, versions::testonly::{ filter_out_base_system_contracts, validation_params, TestedVm, TestedVmForValidation, + TestedVmWithCallTracer, }, vm_latest::{ constants::BOOTLOADER_HEAP_PAGE, @@ -338,3 +340,13 @@ impl Vm (VmExecutionResultAndLogs, Vec) { + let result = Arc::new(OnceCell::new()); + let call_tracer = CallTracer::new(result.clone()).into_tracer_pointer(); + let res = self.inspect(&mut call_tracer.into(), InspectExecutionMode::OneTx); + let traces = result.get().unwrap().clone(); + (res, traces) + } +} diff --git a/core/lib/vm_executor/src/batch/factory.rs b/core/lib/vm_executor/src/batch/factory.rs index 59b610426669..5e375d1f3062 100644 --- a/core/lib/vm_executor/src/batch/factory.rs +++ b/core/lib/vm_executor/src/batch/factory.rs @@ -8,8 +8,8 @@ use zksync_multivm::{ executor::{BatchExecutor, BatchExecutorFactory}, pubdata::PubdataBuilder, storage::{ReadStorage, StoragePtr, StorageView, StorageViewStats}, - utils::DivergenceHandler, - BatchTransactionExecutionResult, BytecodeCompressionError, CompressedBytecodeInfo, + utils::{DivergenceHandler, ShadowMut}, + BatchTransactionExecutionResult, BytecodeCompressionError, Call, CompressedBytecodeInfo, ExecutionResult, FinishedL1Batch, Halt, L1BatchEnv, L2BlockEnv, SystemEnv, VmFactory, VmInterface, VmInterfaceHistoryEnabled, }, @@ -28,6 +28,23 @@ use super::{ }; use crate::shared::{InteractionType, Sealed, STORAGE_METRICS}; +#[doc(hidden)] +pub trait CallTracingTracer: vm_fast::interface::Tracer + Default { + fn into_traces(self) -> Vec; +} + +impl CallTracingTracer for () { + fn into_traces(self) -> Vec { + vec![] + } +} + +impl CallTracingTracer for vm_fast::CallTracer { + fn into_traces(self) -> Vec { + self.into_result() + } +} + /// Encapsulates a tracer used during batch processing. Currently supported tracers are `()` (no-op) and [`TraceCalls`]. /// /// All members of this trait are implementation details. @@ -37,7 +54,7 @@ pub trait BatchTracer: fmt::Debug + 'static + Send + Sealed { const TRACE_CALLS: bool; /// Tracer for the fast VM. #[doc(hidden)] - type Fast: vm_fast::interface::Tracer + Default; + type Fast: CallTracingTracer; } impl Sealed for () {} @@ -56,7 +73,7 @@ impl Sealed for TraceCalls {} impl BatchTracer for TraceCalls { const TRACE_CALLS: bool = true; - type Fast = (); // TODO: change once call tracing is implemented in fast VM + type Fast = vm_fast::CallTracer; } /// The default implementation of [`BatchExecutorFactory`]. @@ -213,13 +230,14 @@ impl BatchVm { tx: Transaction, with_compression: bool, ) -> BatchTransactionExecutionResult { - let call_tracer_result = Arc::new(OnceCell::default()); + let legacy_tracer_result = Arc::new(OnceCell::default()); let legacy_tracer = if Tr::TRACE_CALLS { - vec![CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()] + vec![CallTracer::new(legacy_tracer_result.clone()).into_tracer_pointer()] } else { vec![] }; let mut legacy_tracer = legacy_tracer.into(); + let mut fast_traces = vec![]; let (compression_result, tx_result) = match self { Self::Legacy(vm) => vm.inspect_transaction_with_bytecode_compression( @@ -228,16 +246,34 @@ impl BatchVm { with_compression, ), Self::Fast(vm) => { - let mut tracer = (legacy_tracer.into(), Default::default()); - vm.inspect_transaction_with_bytecode_compression(&mut tracer, tx, with_compression) + let mut tracer = (legacy_tracer.into(), (Tr::Fast::default(), ())); + let res = vm.inspect_transaction_with_bytecode_compression( + &mut tracer, + tx, + with_compression, + ); + let (_, (call_tracer, _)) = tracer; + fast_traces = call_tracer.into_traces(); + res } }; let compressed_bytecodes = compression_result.map(Cow::into_owned); - let call_traces = Arc::try_unwrap(call_tracer_result) + let legacy_traces = Arc::try_unwrap(legacy_tracer_result) .expect("failed extracting call traces") .take() .unwrap_or_default(); + let call_traces = match self { + Self::Legacy(_) => legacy_traces, + Self::Fast(FastVmInstance::Fast(_)) => fast_traces, + Self::Fast(FastVmInstance::Shadowed(vm)) => { + vm.get_custom_mut("call_traces", |r| match r { + ShadowMut::Main(_) => legacy_traces.as_slice(), + ShadowMut::Shadow(_) => fast_traces.as_slice(), + }); + fast_traces + } + }; BatchTransactionExecutionResult { tx_result: Box::new(tx_result), diff --git a/core/lib/vm_interface/src/utils/shadow.rs b/core/lib/vm_interface/src/utils/shadow.rs index d6a6d16c77a0..e4a7aa51f78c 100644 --- a/core/lib/vm_interface/src/utils/shadow.rs +++ b/core/lib/vm_interface/src/utils/shadow.rs @@ -7,16 +7,19 @@ use std::{ sync::Arc, }; -use zksync_types::{StorageKey, StorageLog, StorageLogWithPreviousValue, Transaction}; +use zksync_types::{ + Address, StorageKey, StorageLog, StorageLogWithPreviousValue, Transaction, U256, +}; use super::dump::{DumpingVm, VmDump}; use crate::{ pubdata::PubdataBuilder, storage::{ReadStorage, StoragePtr, StorageView}, tracer::{ValidationError, ValidationTraces}, - BytecodeCompressionResult, CurrentExecutionState, FinishedL1Batch, InspectExecutionMode, - L1BatchEnv, L2BlockEnv, PushTransactionResult, SystemEnv, VmExecutionResultAndLogs, VmFactory, - VmInterface, VmInterfaceHistoryEnabled, VmTrackingContracts, + BytecodeCompressionResult, Call, CallType, CurrentExecutionState, FinishedL1Batch, + InspectExecutionMode, L1BatchEnv, L2BlockEnv, PushTransactionResult, SystemEnv, + VmExecutionResultAndLogs, VmFactory, VmInterface, VmInterfaceHistoryEnabled, + VmTrackingContracts, }; /// Handler for VM divergences. @@ -233,6 +236,64 @@ impl CheckDivergence for Result { } } +/// `PartialEq` for `Call` doesn't compare gas-related fields. Here, we do compare them. +#[derive(Debug, PartialEq)] +struct StrictCall<'a> { + r#type: CallType, + from: Address, + to: Address, + // `gas` / `parent_gas` differ between fast VM and legacy VM during validation + gas_used: u64, + value: U256, + input: &'a [u8], + output: &'a [u8], + error: Option<&'a str>, + revert_reason: Option<&'a str>, +} + +impl<'a> StrictCall<'a> { + fn flatten(calls: &'a [Call]) -> Vec { + let mut flattened = Vec::new(); + Self::flatten_inner(&mut flattened, calls); + flattened + } + + fn flatten_inner(flattened: &mut Vec, calls: &'a [Call]) { + // Depth-first, parents-before-children traversal. + for call in calls { + flattened.push(Self { + r#type: call.r#type, + from: call.from, + to: call.to, + gas_used: call.gas_used, + value: call.value, + input: &call.input, + output: &call.output, + error: call.error.as_deref(), + revert_reason: call.revert_reason.as_deref(), + }); + Self::flatten_inner(flattened, &call.calls); + } + } +} + +impl CheckDivergence for [Call] { + fn check_divergence(&self, other: &Self) -> DivergenceErrors { + let this = StrictCall::flatten(self); + let other = StrictCall::flatten(other); + let mut errors = DivergenceErrors::new(); + + errors.check_match("call_traces", &this, &other); + errors + } +} + +impl CheckDivergence for &T { + fn check_divergence(&self, other: &Self) -> DivergenceErrors { + (**self).check_divergence(*other) + } +} + /// Shadowed VM that executes 2 VMs for each operation and compares their outputs. /// /// If a divergence is detected, the VM state is dumped using [a pluggable handler](Self::set_dump_handler()), @@ -526,7 +587,8 @@ impl DivergenceErrors { } } - fn extend(&mut self, from: Self) { + /// Extends this instance from another set of errors. + pub fn extend(&mut self, from: Self) { self.divergences.extend(from.divergences); } diff --git a/core/node/api_server/src/web3/namespaces/debug.rs b/core/node/api_server/src/web3/namespaces/debug.rs index 98af15032008..180de6b273e5 100644 --- a/core/node/api_server/src/web3/namespaces/debug.rs +++ b/core/node/api_server/src/web3/namespaces/debug.rs @@ -54,6 +54,7 @@ impl DebugNamespace { } } } + pub(crate) fn map_default_call( call: Call, only_top_call: bool, diff --git a/core/node/state_keeper/src/executor/tests/mod.rs b/core/node/state_keeper/src/executor/tests/mod.rs index 219cacc60c85..8d735e9ed920 100644 --- a/core/node/state_keeper/src/executor/tests/mod.rs +++ b/core/node/state_keeper/src/executor/tests/mod.rs @@ -1,18 +1,20 @@ -// FIXME: move storage-agnostic tests to VM executor crate - use assert_matches::assert_matches; use rand::{thread_rng, Rng}; use test_casing::{test_casing, Product}; use zksync_contracts::l2_message_root; use zksync_dal::{ConnectionPool, Core}; -use zksync_multivm::interface::{BatchTransactionExecutionResult, ExecutionResult, Halt}; +use zksync_multivm::interface::{ + BatchTransactionExecutionResult, Call, CallType, ExecutionResult, Halt, +}; use zksync_test_contracts::{Account, TestContract}; use zksync_types::{ - get_nonce_key, utils::storage_key_for_eth_balance, vm::FastVmMode, web3, Execute, PriorityOpId, - H256, L2_MESSAGE_ROOT_ADDRESS, U256, + get_nonce_key, + utils::{deployed_address_create, storage_key_for_eth_balance}, + vm::FastVmMode, + web3, Execute, PriorityOpId, H256, L2_MESSAGE_ROOT_ADDRESS, U256, }; -use self::tester::{AccountExt, StorageSnapshot, TestConfig, Tester}; +use self::tester::{AccountExt, StorageSnapshot, TestConfig, Tester, TRANSFER_VALUE}; mod read_storage_factory; mod tester; @@ -854,9 +856,9 @@ async fn execute_tx_with_large_packable_bytecode(vm_mode: FastVmMode) { executor.finish_batch().await.unwrap(); } -#[test_casing(2, [FastVmMode::Old, FastVmMode::Shadow])] // new VM doesn't support call tracing yet +#[test_casing(3, FAST_VM_MODES)] #[tokio::test] -async fn execute_tx_with_call_traces(vm_mode: FastVmMode) { +async fn execute_txs_with_call_traces(vm_mode: FastVmMode) { let connection_pool = ConnectionPool::::constrained_test_pool(1).await; let mut alice = Account::random(); let mut tester = Tester::with_config( @@ -876,4 +878,35 @@ async fn execute_tx_with_call_traces(vm_mode: FastVmMode) { assert_matches!(res.tx_result.result, ExecutionResult::Success { .. }); assert!(!res.call_traces.is_empty()); + + find_first_call(&res.call_traces, &|call| { + call.from == alice.address && call.value == TRANSFER_VALUE.into() + }) + .expect("no transfer call"); + + let deploy_tx = alice.deploy_loadnext_tx().tx; + let res = executor.execute_tx(deploy_tx).await.unwrap(); + assert_matches!(res.tx_result.result, ExecutionResult::Success { .. }); + assert!(!res.call_traces.is_empty()); + + let create_call = find_first_call(&res.call_traces, &|call| { + call.from == alice.address && call.r#type == CallType::Create + }) + .expect("no create call"); + + let expected_address = deployed_address_create(alice.address, 0.into()); + assert_eq!(create_call.to, expected_address); + assert!(!create_call.input.is_empty()); +} + +fn find_first_call<'a>(calls: &'a [Call], predicate: &impl Fn(&Call) -> bool) -> Option<&'a Call> { + for call in calls { + if predicate(call) { + return Some(call); + } + if let Some(call) = find_first_call(&call.calls, predicate) { + return Some(call); + } + } + None } diff --git a/core/node/state_keeper/src/executor/tests/tester.rs b/core/node/state_keeper/src/executor/tests/tester.rs index 8b6df7f04840..6c5015fbca46 100644 --- a/core/node/state_keeper/src/executor/tests/tester.rs +++ b/core/node/state_keeper/src/executor/tests/tester.rs @@ -1,7 +1,7 @@ //! Testing harness for the batch executor. //! Contains helper functionality to initialize test context and perform tests without too much boilerplate. -use std::{collections::HashMap, fmt::Debug, str::FromStr, sync::Arc}; +use std::{collections::HashMap, fmt::Debug, sync::Arc}; use assert_matches::assert_matches; use tempfile::TempDir; @@ -42,14 +42,12 @@ use zksync_vm_executor::batch::{MainBatchExecutorFactory, TraceCalls}; use super::{read_storage_factory::RocksdbStorageFactory, StorageType}; use crate::{ - testonly::{self, BASE_SYSTEM_CONTRACTS}, + testonly::{self, apply_genesis_logs, BASE_SYSTEM_CONTRACTS}, tests::{default_l1_batch_env, default_system_env}, AsyncRocksdbCache, }; -fn get_da_contract_address() -> Address { - Address::from_str("7726827caac94a7f9e1b160f7ea819f172f7b6f9").unwrap() -} +pub(super) const TRANSFER_VALUE: u64 = 123_456_789; /// Representation of configuration parameters used by the state keeper. /// Has sensible defaults for most tests, each of which can be overridden. @@ -294,7 +292,7 @@ impl Tester { .await .unwrap(); - // Also setting up the da for tests + // Also setting up the DA for tests Self::setup_da(&mut storage).await; } } @@ -334,37 +332,28 @@ impl Tester { } } - pub async fn setup_contract<'a>( - con: &mut Connection<'a, Core>, - address: Address, - code: Vec, - ) { + async fn setup_contract(conn: &mut Connection<'_, Core>, address: Address, code: Vec) { let hash: H256 = BytecodeHash::for_bytecode(&code).value(); let known_code_key = get_known_code_key(&hash); let code_key = get_code_key(&address); - let logs = vec![ - StorageLog::new_write_log(known_code_key, H256::from_low_u64_be(1u64)), + let logs = [ + StorageLog::new_write_log(known_code_key, H256::from_low_u64_be(1)), StorageLog::new_write_log(code_key, hash), ]; + apply_genesis_logs(conn, &logs).await; - for log in logs { - apply_genesis_log(con, log).await; - } - - let mut factory_deps = HashMap::new(); - factory_deps.insert(hash, code); - - con.factory_deps_dal() + let factory_deps = HashMap::from([(hash, code)]); + conn.factory_deps_dal() .insert_factory_deps(L2BlockNumber(0), &factory_deps) .await .unwrap(); } - async fn setup_da<'a>(con: &mut Connection<'a, Core>) { + async fn setup_da(conn: &mut Connection<'_, Core>) { Self::setup_contract( - con, - get_da_contract_address(), + conn, + Address::repeat_byte(0x23), l2_rollup_da_validator_bytecode(), ) .await; @@ -442,6 +431,7 @@ impl AccountExt for Account { TxType::L2, ) } + fn l1_execute(&mut self, serial_id: PriorityOpId) -> Transaction { self.get_l1_tx(Execute::transfer(Address::random(), 0.into()), serial_id.0) } @@ -505,7 +495,7 @@ impl AccountExt for Account { /// Automatically increments nonce of the account. fn execute_with_gas_limit(&mut self, gas_limit: u32) -> Transaction { self.get_l2_tx_for_execute( - Execute::transfer(Address::random(), 0.into()), + Execute::transfer(Address::random(), TRANSFER_VALUE.into()), Some(testonly::fee(gas_limit)), ) } @@ -766,25 +756,3 @@ impl StorageSnapshot { snapshot } } - -async fn apply_genesis_log<'a>(storage: &mut Connection<'a, Core>, log: StorageLog) { - storage - .storage_logs_dal() - .append_storage_logs(L2BlockNumber(0), &[log]) - .await - .unwrap(); - - if storage - .storage_logs_dedup_dal() - .filter_written_slots(&[log.key.hashed_key()]) - .await - .unwrap() - .is_empty() - { - storage - .storage_logs_dedup_dal() - .insert_initial_writes(L1BatchNumber(0), &[log.key.hashed_key()]) - .await - .unwrap(); - } -} diff --git a/core/node/state_keeper/src/testonly/mod.rs b/core/node/state_keeper/src/testonly/mod.rs index c0f3707f9455..0484fe3198fd 100644 --- a/core/node/state_keeper/src/testonly/mod.rs +++ b/core/node/state_keeper/src/testonly/mod.rs @@ -1,7 +1,7 @@ //! Test utilities that can be used for testing sequencer that may //! be useful outside of this crate. -use std::collections::HashMap; +use std::collections::HashSet; use async_trait::async_trait; use once_cell::sync::Lazy; @@ -15,10 +15,10 @@ use zksync_multivm::interface::{ }; use zksync_state::OwnedStorage; use zksync_types::{ - bytecode::BytecodeHash, commitment::PubdataParams, fee::Fee, get_code_key, get_known_code_key, - u256_to_h256, utils::storage_key_for_standard_token_balance, AccountTreeId, Address, - L1BatchNumber, L2BlockNumber, StorageLog, Transaction, H256, L2_BASE_TOKEN_ADDRESS, - SYSTEM_CONTEXT_MINIMAL_BASE_FEE, U256, + commitment::PubdataParams, fee::Fee, u256_to_h256, + utils::storage_key_for_standard_token_balance, AccountTreeId, Address, L1BatchNumber, + L2BlockNumber, StorageLog, Transaction, L2_BASE_TOKEN_ADDRESS, SYSTEM_CONTEXT_MINIMAL_BASE_FEE, + U256, }; pub mod test_batch_executor; @@ -76,25 +76,28 @@ impl BatchExecutor for MockBatchExecutor { } } -async fn apply_genesis_log<'a>(storage: &mut Connection<'a, Core>, log: StorageLog) { +pub(crate) async fn apply_genesis_logs(storage: &mut Connection<'_, Core>, logs: &[StorageLog]) { storage .storage_logs_dal() - .append_storage_logs(L2BlockNumber(0), &[log]) + .append_storage_logs(L2BlockNumber(0), logs) .await .unwrap(); - if storage + + let all_hashed_keys: Vec<_> = logs.iter().map(|log| log.key.hashed_key()).collect(); + let repeated_writes = storage .storage_logs_dedup_dal() - .filter_written_slots(&[log.key.hashed_key()]) + .filter_written_slots(&all_hashed_keys) .await - .unwrap() - .is_empty() - { - storage - .storage_logs_dedup_dal() - .insert_initial_writes(L1BatchNumber(0), &[log.key.hashed_key()]) - .await - .unwrap(); - } + .unwrap(); + let initial_writes: Vec<_> = HashSet::from_iter(all_hashed_keys) + .difference(&repeated_writes) + .copied() + .collect(); + storage + .storage_logs_dedup_dal() + .insert_initial_writes(L1BatchNumber(0), &initial_writes) + .await + .unwrap(); } /// Adds funds for specified account list. @@ -103,43 +106,18 @@ pub async fn fund(pool: &ConnectionPool, addresses: &[Address]) { let mut storage = pool.connection().await.unwrap(); let eth_amount = U256::from(10u32).pow(U256::from(32)); //10^32 wei - - for address in addresses { - let key = storage_key_for_standard_token_balance( - AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), - address, - ); - let value = u256_to_h256(eth_amount); - let storage_log = StorageLog::new_write_log(key, value); - - apply_genesis_log(&mut storage, storage_log).await; - } -} - -pub async fn setup_contract(pool: &ConnectionPool, address: Address, code: Vec) { - let mut storage = pool.connection().await.unwrap(); - - let hash: H256 = BytecodeHash::for_bytecode(&code).value(); - let known_code_key = get_known_code_key(&hash); - let code_key = get_code_key(&address); - - let logs = vec![ - StorageLog::new_write_log(known_code_key, H256::from_low_u64_be(1u64)), - StorageLog::new_write_log(code_key, hash), - ]; - - for log in logs { - apply_genesis_log(&mut storage, log).await; - } - - let mut factory_deps = HashMap::new(); - factory_deps.insert(hash, code); - - storage - .factory_deps_dal() - .insert_factory_deps(L2BlockNumber(0), &factory_deps) - .await - .unwrap(); + let storage_logs: Vec<_> = addresses + .iter() + .map(|address| { + let key = storage_key_for_standard_token_balance( + AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), + address, + ); + StorageLog::new_write_log(key, u256_to_h256(eth_amount)) + }) + .collect(); + + apply_genesis_logs(&mut storage, &storage_logs).await; } pub(crate) const DEFAULT_GAS_PER_PUBDATA: u32 = 10000; From 67663bffaad4712a782f21279c07feeefb68e828 Mon Sep 17 00:00:00 2001 From: Artem Makhortov <13339874+artmakh@users.noreply.github.com> Date: Mon, 20 Jan 2025 19:25:58 +0700 Subject: [PATCH 15/52] fix(ci): Update github-hosted runner label (#3506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Update github-hosted runner label ## Why ❔ Github update runner OS version ## 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`. --- .github/workflows/protobuf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/protobuf.yaml b/.github/workflows/protobuf.yaml index ba667430dd04..62748c79251a 100644 --- a/.github/workflows/protobuf.yaml +++ b/.github/workflows/protobuf.yaml @@ -31,7 +31,7 @@ env: jobs: compatibility: - runs-on: [ubuntu-22.04-github-hosted-16core] + runs-on: [ubuntu-24.04-github-hosted-16core] steps: - uses: mozilla-actions/sccache-action@89e9040de88b577a072e3760aaf59f585da083af # v0.0.5 From 995e583aa9b4ef6e0d8697fbb040e4b991a4248d Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 20 Jan 2025 16:56:09 +0000 Subject: [PATCH 16/52] fix: copy special case to fast VM call tracer (#3509) --- core/lib/multivm/src/versions/vm_fast/tracers/calls.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs b/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs index 727e33ebb8d2..2413e5916a25 100644 --- a/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs +++ b/core/lib/multivm/src/versions/vm_fast/tracers/calls.rs @@ -50,7 +50,11 @@ impl Tracer for CallTracer { let current_gas = state.current_frame().gas() as u64; let from = state.current_frame().caller(); let to = state.current_frame().address(); - let input = read_raw_fat_pointer(state, state.read_register(1).0); + let input = if current_gas == 0 { + vec![] + } else { + read_raw_fat_pointer(state, state.read_register(1).0) + }; let value = U256::from(state.current_frame().context_u128()); let ty = match ty { CallingMode::Normal => CallType::Call(FarCallOpcode::Normal), From b7618e1ca78f92dacce617d7f56d49779d8123d9 Mon Sep 17 00:00:00 2001 From: Tahsin Tunan Date: Mon, 20 Jan 2025 23:37:24 +0600 Subject: [PATCH 17/52] chore: Localize feature import to crate for circuit prover (#3502) ### Description This PR resolves the build failure in [`zksync_prover_job_processor`](https://github.com/matter-labs/zksync-era/tree/main/prover/crates/lib/prover_job_processor) caused by the following error: ``` error[E0432]: unresolved import tokio::task::JoinHandle --> crates/lib/prover_job_processor/src/job_runner.rs:1:5 | 1 | use tokio::task::JoinHandle; | ^^^^^^^^^^^^^^^^^^^^^^^ no JoinHandle in task ``` The error occurred because `JoinHandle` was gated behind the `rt` feature in Tokio, which was disabled. Enabling the feature resolves the import and fixes the build. --- prover/Cargo.toml | 6 +++--- prover/crates/lib/circuit_prover_service/Cargo.toml | 6 ++---- prover/crates/lib/prover_job_processor/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/prover/Cargo.toml b/prover/Cargo.toml index aed8cbfdc723..c1d006f2a759 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -71,13 +71,13 @@ vise = "0.2.0" circuit_definitions = "=0.150.19" circuit_sequencer_api = "=0.150.19" zkevm_test_harness = "=0.150.19" -proof-compression-gpu = { package = "proof-compression", version = "=0.152.10"} -fflonk-gpu = { package = "fflonk-cuda", version = "=0.152.10"} +proof-compression-gpu = { package = "proof-compression", version = "=0.152.10" } +fflonk-gpu = { package = "fflonk-cuda", version = "=0.152.10" } fflonk = "=0.30.12" franklin-crypto = "=0.30.12" # GPU proving dependencies -wrapper_prover = { package = "zksync-wrapper-prover", version = "=0.152.10"} +wrapper_prover = { package = "zksync-wrapper-prover", version = "=0.152.10" } shivini = "=0.152.10" boojum-cuda = "=0.152.10" diff --git a/prover/crates/lib/circuit_prover_service/Cargo.toml b/prover/crates/lib/circuit_prover_service/Cargo.toml index ca7d1ede02f1..3b152528ab68 100644 --- a/prover/crates/lib/circuit_prover_service/Cargo.toml +++ b/prover/crates/lib/circuit_prover_service/Cargo.toml @@ -13,7 +13,7 @@ categories.workspace = true [dependencies] zksync_prover_job_processor.workspace = true zksync_prover_fri_types.workspace = true -zksync_prover_keystore.workspace = true +zksync_prover_keystore = { workspace = true, features = ["gpu-light"] } zksync_prover_dal.workspace = true zksync_types.workspace = true zksync_object_store.workspace = true @@ -24,8 +24,6 @@ tokio = { workspace = true, features = ["macros", "time"] } tokio-util.workspace = true tracing.workspace = true -shivini = { workspace = true, features = [ - "circuit_definitions", -] } +shivini = { workspace = true, features = ["circuit_definitions"] } zkevm_test_harness.workspace = true vise.workspace = true diff --git a/prover/crates/lib/prover_job_processor/Cargo.toml b/prover/crates/lib/prover_job_processor/Cargo.toml index 5197b33b1f95..fea4c6195fd7 100644 --- a/prover/crates/lib/prover_job_processor/Cargo.toml +++ b/prover/crates/lib/prover_job_processor/Cargo.toml @@ -14,7 +14,7 @@ categories.workspace = true async-trait.workspace = true anyhow.workspace = true futures.workspace = true -tokio.workspace = true +tokio = { workspace = true, features = ["rt"] } tokio-stream.workspace = true tokio-util.workspace = true tracing.workspace = true From 2b896dc3bcb802b628da0539b0ae6cd5efdde036 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:05:17 +0200 Subject: [PATCH 18/52] fix(docker): include l1 zkout in images (#3510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ include l1 zkout in images ## Why ❔ it's required for genesis ## 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`. --- docker/external-node/Dockerfile | 1 + docker/server-v2/Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/docker/external-node/Dockerfile b/docker/external-node/Dockerfile index 3c968b896e43..31fab51719a5 100644 --- a/docker/external-node/Dockerfile +++ b/docker/external-node/Dockerfile @@ -25,6 +25,7 @@ COPY --from=builder /usr/local/cargo/bin/sqlx /usr/bin COPY --from=builder /usr/src/zksync/docker/external-node/entrypoint.sh /usr/bin COPY contracts/system-contracts/zkout/ /contracts/system-contracts/zkout/ COPY contracts/l1-contracts/out/ /contracts/l1-contracts/out/ +COPY contracts/l1-contracts/zkout/ /contracts/l1-contracts/zkout/ COPY contracts/l2-contracts/zkout/ /contracts/l2-contracts/zkout/ COPY etc/tokens/ /etc/tokens/ COPY etc/ERC20/ /etc/ERC20/ diff --git a/docker/server-v2/Dockerfile b/docker/server-v2/Dockerfile index 5855bab8d7ab..869f6bdb463f 100644 --- a/docker/server-v2/Dockerfile +++ b/docker/server-v2/Dockerfile @@ -33,6 +33,7 @@ COPY --from=builder /usr/src/zksync/core/target/release/block_reverter /usr/bin COPY --from=builder /usr/src/zksync/core/target/release/merkle_tree_consistency_checker /usr/bin COPY contracts/system-contracts/zkout/ /contracts/system-contracts/zkout/ COPY contracts/l1-contracts/out/ /contracts/l1-contracts/out/ +COPY contracts/l1-contracts/zkout/ /contracts/l1-contracts/zkout/ COPY contracts/l2-contracts/zkout/ /contracts/l2-contracts/zkout/ COPY etc/tokens/ /etc/tokens/ COPY etc/ERC20/ /etc/ERC20/ From c140ba8f57caabf9c9bdd4bd8c9743a9ccf668be Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 21 Jan 2025 08:12:09 +0100 Subject: [PATCH 19/52] fix(gateway): erc20 workaround for gateway upgrade (#3511) 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/node/eth_watch/src/client.rs | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/core/node/eth_watch/src/client.rs b/core/node/eth_watch/src/client.rs index 03b22df55995..1a48898c131d 100644 --- a/core/node/eth_watch/src/client.rs +++ b/core/node/eth_watch/src/client.rs @@ -15,7 +15,7 @@ use zksync_types::{ abi::ZkChainSpecificUpgradeData, api::{ChainAggProof, Log}, ethabi::{self, decode, encode, Contract, ParamType}, - web3::{keccak256, BlockId, BlockNumber, CallRequest, Filter, FilterBuilder}, + web3::{keccak256, BlockId, BlockNumber, Filter, FilterBuilder}, Address, L1BatchNumber, L2ChainId, SLChainId, H256, L2_NATIVE_TOKEN_VAULT_ADDRESS, SHARED_BRIDGE_ETHER_TOKEN_ADDRESS, U256, U64, }; @@ -511,28 +511,9 @@ where if base_token_l1_address == SHARED_BRIDGE_ETHER_TOKEN_ADDRESS { (String::from("Ether"), String::from("ETH")) } else { - // TODO(EVM-934): support non-standard tokens. - let selectors: [[u8; 4]; 2] = [ - ethabi::short_signature("name", &[]), - ethabi::short_signature("symbol", &[]), - ]; - let types: [ParamType; 2] = [ParamType::String, ParamType::String]; - - let mut decoded_result = vec![]; - for (selector, param_type) in selectors.into_iter().zip(types.into_iter()) { - let request = CallRequest { - to: Some(base_token_l1_address), - data: Some(selector.into()), - ..Default::default() - }; - let result = self.client.call_contract_function(request, None).await?; - // Base tokens are expected to support erc20 metadata - let mut token = ethabi::decode(&[param_type], &result.0) - .expect("base token does not support erc20 metadata"); - decoded_result.push(token.pop().unwrap()); - } - - (decoded_result[0].to_string(), decoded_result[1].to_string()) + // Due to an issue in the upgrade process, the automatically + // deployed wrapped base tokens will contain generic names + (String::from("Base Token"), String::from("BT")) }; let base_token_asset_id = encode_ntv_asset_id( From 86db9ff6aed4017b0914ff6795295c45246282bf Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:34:40 +0200 Subject: [PATCH 20/52] fix(api): use scaled version of batch fee input in `get_batch_fee_input_impl` (#3507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ - Use scaled version of batch fee input in get_batch_fee_input_impl - Improve adjust_pubdata_price_for_tx so it assumes optimistic scenario ## Why ❔ Scaled fee input should be used for every user-facing API method ## 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/multivm/src/utils/mod.rs | 14 +++++++++----- core/node/api_server/src/web3/namespaces/zks.rs | 4 +--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/lib/multivm/src/utils/mod.rs b/core/lib/multivm/src/utils/mod.rs index 7bb907efc9b8..825874747682 100644 --- a/core/lib/multivm/src/utils/mod.rs +++ b/core/lib/multivm/src/utils/mod.rs @@ -113,13 +113,13 @@ pub fn adjust_pubdata_price_for_tx( ) -> BatchFeeInput { // If no max base fee was provided, we just use the maximal one for convenience. let max_base_fee = max_base_fee.unwrap_or(U256::MAX); - let desired_gas_per_pubdata = + let bounded_tx_gas_per_pubdata_limit = tx_gas_per_pubdata_limit.min(get_max_gas_per_pubdata_byte(vm_version).into()); let (current_base_fee, current_gas_per_pubdata) = derive_base_fee_and_gas_per_pubdata(batch_fee_input, vm_version); - if U256::from(current_gas_per_pubdata) <= desired_gas_per_pubdata + if U256::from(current_gas_per_pubdata) <= bounded_tx_gas_per_pubdata_limit && U256::from(current_base_fee) <= max_base_fee { // gas per pubdata is already smaller than or equal to `tx_gas_per_pubdata_limit`. @@ -138,8 +138,9 @@ pub fn adjust_pubdata_price_for_tx( // `gasPerPubdata = ceil(17 * l1gasprice / fair_l2_gas_price)` // `gasPerPubdata <= 17 * l1gasprice / fair_l2_gas_price + 1` // `fair_l2_gas_price(gasPerPubdata - 1) / 17 <= l1gasprice` - let new_l1_gas_price = - fair_l2_gas_price * (desired_gas_per_pubdata - U256::from(1u32)) / U256::from(17); + let new_l1_gas_price = fair_l2_gas_price + * bounded_tx_gas_per_pubdata_limit.saturating_sub(U256::from(1u32)) + / U256::from(17); BatchFeeInput::L1Pegged(L1PeggedBatchFeeModelInput { l1_gas_price: new_l1_gas_price.as_u64(), @@ -154,11 +155,14 @@ pub fn adjust_pubdata_price_for_tx( current_l2_fair_gas_price }; + // We want to adjust gas per pubdata to be min(bounded_tx_gas_per_pubdata_limit, current_gas_per_pubdata). + let desired_gas_per_pubdata = + bounded_tx_gas_per_pubdata_limit.min(U256::from(current_gas_per_pubdata)); // `gasPerPubdata = ceil(fair_pubdata_price / fair_l2_gas_price)` // `gasPerPubdata <= fair_pubdata_price / fair_l2_gas_price + 1` // `fair_l2_gas_price(gasPerPubdata - 1) <= fair_pubdata_price` let new_fair_pubdata_price = - fair_l2_gas_price * (desired_gas_per_pubdata - U256::from(1u32)); + fair_l2_gas_price * desired_gas_per_pubdata.saturating_sub(U256::from(1u32)); BatchFeeInput::PubdataIndependent(PubdataIndependentBatchFeeModelInput { fair_pubdata_price: new_fair_pubdata_price.as_u64(), diff --git a/core/node/api_server/src/web3/namespaces/zks.rs b/core/node/api_server/src/web3/namespaces/zks.rs index b272f7c443e9..9f7c5662a631 100644 --- a/core/node/api_server/src/web3/namespaces/zks.rs +++ b/core/node/api_server/src/web3/namespaces/zks.rs @@ -677,9 +677,7 @@ impl ZksNamespace { Ok(self .state .tx_sender - .0 - .batch_fee_input_provider - .get_batch_fee_input() + .scaled_batch_fee_input() .await? .into_pubdata_independent()) } From 52de75845caf4e3ce981c2d761bdeffbc4ba1af4 Mon Sep 17 00:00:00 2001 From: zksync-era-bot <147085853+zksync-era-bot@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:48:49 +0400 Subject: [PATCH 21/52] chore(main): release core 26.1.0 (#3499) :robot: I have created a release *beep* *boop* --- ## [26.1.0](https://github.com/matter-labs/zksync-era/compare/core-v26.0.0...core-v26.1.0) (2025-01-21) ### Features * update l2 erc20 bridge address in updater as well ([#3500](https://github.com/matter-labs/zksync-era/issues/3500)) ([fe3c7b2](https://github.com/matter-labs/zksync-era/commit/fe3c7b2583bc4f9277e186334e5822ddf95bdcd0)) * **vm:** Implement call tracing for fast VM ([#2905](https://github.com/matter-labs/zksync-era/issues/2905)) ([731b824](https://github.com/matter-labs/zksync-era/commit/731b8240abd4c0cfa42f2ce89c23f8ebf67e1bf2)) ### Bug Fixes * copy special case to fast VM call tracer ([#3509](https://github.com/matter-labs/zksync-era/issues/3509)) ([995e583](https://github.com/matter-labs/zksync-era/commit/995e583aa9b4ef6e0d8697fbb040e4b991a4248d)) * fix execute encoding for transactions ([#3501](https://github.com/matter-labs/zksync-era/issues/3501)) ([4c381a8](https://github.com/matter-labs/zksync-era/commit/4c381a84346f8ab88d3f01dc2848c7fb5f2b788d)) * **gateway:** erc20 workaround for gateway upgrade ([#3511](https://github.com/matter-labs/zksync-era/issues/3511)) ([c140ba8](https://github.com/matter-labs/zksync-era/commit/c140ba8f57caabf9c9bdd4bd8c9743a9ccf668be)) ### Performance Improvements * optimize get_unsealed_l1_batch_inner ([#3491](https://github.com/matter-labs/zksync-era/issues/3491)) ([9b121c9](https://github.com/matter-labs/zksync-era/commit/9b121c96bbb2e53be74aa81e0ca250ce9251f8db)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: zksync-era-bot --- .github/release-please/manifest.json | 2 +- core/CHANGELOG.md | 20 ++++++++++++++++++++ core/Cargo.lock | 2 +- core/bin/external_node/Cargo.toml | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index 782c7a30086c..0ba598bb0516 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,5 +1,5 @@ { - "core": "26.0.0", + "core": "26.1.0", "prover": "17.1.1", "zkstack_cli": "0.1.2" } diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index d0491b179035..256c83058ee2 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [26.1.0](https://github.com/matter-labs/zksync-era/compare/core-v26.0.0...core-v26.1.0) (2025-01-21) + + +### Features + +* update l2 erc20 bridge address in updater as well ([#3500](https://github.com/matter-labs/zksync-era/issues/3500)) ([fe3c7b2](https://github.com/matter-labs/zksync-era/commit/fe3c7b2583bc4f9277e186334e5822ddf95bdcd0)) +* **vm:** Implement call tracing for fast VM ([#2905](https://github.com/matter-labs/zksync-era/issues/2905)) ([731b824](https://github.com/matter-labs/zksync-era/commit/731b8240abd4c0cfa42f2ce89c23f8ebf67e1bf2)) + + +### Bug Fixes + +* copy special case to fast VM call tracer ([#3509](https://github.com/matter-labs/zksync-era/issues/3509)) ([995e583](https://github.com/matter-labs/zksync-era/commit/995e583aa9b4ef6e0d8697fbb040e4b991a4248d)) +* fix execute encoding for transactions ([#3501](https://github.com/matter-labs/zksync-era/issues/3501)) ([4c381a8](https://github.com/matter-labs/zksync-era/commit/4c381a84346f8ab88d3f01dc2848c7fb5f2b788d)) +* **gateway:** erc20 workaround for gateway upgrade ([#3511](https://github.com/matter-labs/zksync-era/issues/3511)) ([c140ba8](https://github.com/matter-labs/zksync-era/commit/c140ba8f57caabf9c9bdd4bd8c9743a9ccf668be)) + + +### Performance Improvements + +* optimize get_unsealed_l1_batch_inner ([#3491](https://github.com/matter-labs/zksync-era/issues/3491)) ([9b121c9](https://github.com/matter-labs/zksync-era/commit/9b121c96bbb2e53be74aa81e0ca250ce9251f8db)) + ## [26.0.0](https://github.com/matter-labs/zksync-era/compare/core-v25.4.0...core-v26.0.0) (2025-01-17) diff --git a/core/Cargo.lock b/core/Cargo.lock index 0c7b38d2d163..d064b2a3d7bc 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -11994,7 +11994,7 @@ dependencies = [ [[package]] name = "zksync_external_node" -version = "26.0.0" +version = "26.1.0" dependencies = [ "anyhow", "assert_matches", diff --git a/core/bin/external_node/Cargo.toml b/core/bin/external_node/Cargo.toml index c8b63a12f70f..f9e494a62780 100644 --- a/core/bin/external_node/Cargo.toml +++ b/core/bin/external_node/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zksync_external_node" description = "Non-validator ZKsync node" -version = "26.0.0" # x-release-please-version +version = "26.1.0" # x-release-please-version edition.workspace = true authors.workspace = true homepage.workspace = true From 6fcecb9daebfe1e06a29951613b54a3f22e6087c Mon Sep 17 00:00:00 2001 From: Yury Akudovich Date: Tue, 21 Jan 2025 14:49:43 +0100 Subject: [PATCH 22/52] ci: Remove prover-gpu-fri and witness-vector-generator from build (#3513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Remove prover-gpu-fri and witness-vector-generator from build. ## Why ❔ To cleanup unneeded builds. ## 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. - [ ] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. ref ZKD-2084 --- ...r.yml => build-circuit-prover-gpu-gar.yml} | 28 +------------ .github/workflows/build-docker-from-tag.yml | 4 +- .../build-proof-fri-gpu-compressor-gar.yml | 4 +- .github/workflows/build-prover-template.yml | 41 ------------------- .github/workflows/release-test-stage.yml | 4 +- 5 files changed, 7 insertions(+), 74 deletions(-) rename .github/workflows/{build-prover-fri-gpu-gar-and-circuit-prover-gpu-gar.yml => build-circuit-prover-gpu-gar.yml} (67%) diff --git a/.github/workflows/build-prover-fri-gpu-gar-and-circuit-prover-gpu-gar.yml b/.github/workflows/build-circuit-prover-gpu-gar.yml similarity index 67% rename from .github/workflows/build-prover-fri-gpu-gar-and-circuit-prover-gpu-gar.yml rename to .github/workflows/build-circuit-prover-gpu-gar.yml index 30990889caf6..a8e86d545c9f 100644 --- a/.github/workflows/build-prover-fri-gpu-gar-and-circuit-prover-gpu-gar.yml +++ b/.github/workflows/build-circuit-prover-gpu-gar.yml @@ -27,7 +27,7 @@ jobs: - name: Download Setup data run: | - gsutil -m rsync -r gs://matterlabs-setup-data-us/${{ inputs.setup_keys_id }} docker/prover-gpu-fri-gar + gsutil -m rsync -r gs://matterlabs-setup-data-us/${{ inputs.setup_keys_id }} docker/circuit-prover-gpu-gar - name: Login to us-central1 GAR run: | @@ -47,32 +47,6 @@ jobs: run: | gcloud auth print-access-token --lifetime=7200 --impersonate-service-account=gha-ci-runners@matterlabs-infra.iam.gserviceaccount.com | docker login -u oauth2accesstoken --password-stdin https://europe-docker.pkg.dev - - name: Build and push prover-gpu-fri-gar - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 - with: - context: docker/prover-gpu-fri-gar - build-args: | - PROVER_IMAGE=${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} - push: true - tags: | - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/prover-fri-gpu-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} - - - name: Build and push prover-gpu-fri-gar to Asia GAR - run: | - docker buildx imagetools create \ - --tag asia-docker.pkg.dev/matterlabs-infra/matterlabs-docker/prover-fri-gpu-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} \ - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/prover-fri-gpu-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} - - - name: Build and push prover-gpu-fri-gar to Europe GAR - run: | - docker buildx imagetools create \ - --tag europe-docker.pkg.dev/matterlabs-infra/matterlabs-docker/prover-fri-gpu-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} \ - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/prover-fri-gpu-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} - - - name: Move Setup data from prover-gpu-fri-gar to circuit-prover-gpu-gar - run: | - mv -v docker/prover-gpu-fri-gar/*.bin docker/circuit-prover-gpu-gar/ - - name: Build and push circuit-prover-gpu-gar uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 with: diff --git a/.github/workflows/build-docker-from-tag.yml b/.github/workflows/build-docker-from-tag.yml index 34f4b55903d2..3d637a224a99 100644 --- a/.github/workflows/build-docker-from-tag.yml +++ b/.github/workflows/build-docker-from-tag.yml @@ -110,10 +110,10 @@ jobs: DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - build-gar-prover-fri-gpu-and-circuit-prover-gpu-gar: + build-circuit-prover-gpu-gar: name: Build GAR prover FRI GPU needs: [setup, build-push-prover-images] - uses: ./.github/workflows/build-prover-fri-gpu-gar-and-circuit-prover-gpu-gar.yml + uses: ./.github/workflows/build-circuit-prover-gpu-gar.yml if: contains(github.ref_name, 'prover') with: setup_keys_id: ${{ needs.setup.outputs.prover_fri_gpu_key_id }} diff --git a/.github/workflows/build-proof-fri-gpu-compressor-gar.yml b/.github/workflows/build-proof-fri-gpu-compressor-gar.yml index e11a49640369..05255eeb2c8d 100644 --- a/.github/workflows/build-proof-fri-gpu-compressor-gar.yml +++ b/.github/workflows/build-proof-fri-gpu-compressor-gar.yml @@ -58,13 +58,13 @@ jobs: tags: | us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/proof-fri-gpu-compressor-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} - - name: Build and push prover-gpu-fri-gar to Asia GAR + - name: Build and push proof-fri-gpu-compressor-gar to Asia GAR run: | docker buildx imagetools create \ --tag asia-docker.pkg.dev/matterlabs-infra/matterlabs-docker/proof-fri-gpu-compressor-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} \ us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/proof-fri-gpu-compressor-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} - - name: Build and push prover-gpu-fri-gar to Europe GAR + - name: Build and push proof-fri-gpu-compressor-gar to Europe GAR run: | docker buildx imagetools create \ --tag europe-docker.pkg.dev/matterlabs-infra/matterlabs-docker/proof-fri-gpu-compressor-gar:2.0-${{ inputs.protocol_version }}-${{ inputs.image_tag_suffix }} \ diff --git a/.github/workflows/build-prover-template.yml b/.github/workflows/build-prover-template.yml index 7d2920a2af4e..dc097b240b70 100644 --- a/.github/workflows/build-prover-template.yml +++ b/.github/workflows/build-prover-template.yml @@ -98,8 +98,6 @@ jobs: matrix: components: - witness-generator - - prover-gpu-fri - - witness-vector-generator - prover-fri-gateway - prover-job-monitor - proof-fri-gpu-compressor @@ -201,42 +199,3 @@ jobs: docker push us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.components }}:latest docker push ghcr.io/${{ github.repository_owner }}/${{ matrix.components }}:latest docker push matterlabs/${{ matrix.components }}:latest - - copy-images: - name: Copy images between docker registries - needs: [build-images, get-protocol-version] - env: - PROTOCOL_VERSION: ${{ needs.get-protocol-version.outputs.protocol_version }} - runs-on: matterlabs-ci-runner - if: ${{ inputs.action == 'push' }} - strategy: - matrix: - component: - - witness-vector-generator - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - - - name: Login to us-central1 GAR - run: | - gcloud auth print-access-token --lifetime=7200 --impersonate-service-account=gha-ci-runners@matterlabs-infra.iam.gserviceaccount.com | docker login -u oauth2accesstoken --password-stdin https://us-docker.pkg.dev - - - name: Login and push to Asia GAR - run: | - gcloud auth print-access-token --lifetime=7200 --impersonate-service-account=gha-ci-runners@matterlabs-infra.iam.gserviceaccount.com | docker login -u oauth2accesstoken --password-stdin https://asia-docker.pkg.dev - docker buildx imagetools create \ - --tag asia-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ env.PROTOCOL_VERSION }}-${{ inputs.image_tag_suffix }} \ - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ env.PROTOCOL_VERSION }}-${{ inputs.image_tag_suffix }} - docker buildx imagetools create \ - --tag asia-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ inputs.image_tag_suffix }} \ - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ inputs.image_tag_suffix }} - - - name: Login and push to Europe GAR - run: | - gcloud auth print-access-token --lifetime=7200 --impersonate-service-account=gha-ci-runners@matterlabs-infra.iam.gserviceaccount.com | docker login -u oauth2accesstoken --password-stdin https://europe-docker.pkg.dev - docker buildx imagetools create \ - --tag europe-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ env.PROTOCOL_VERSION }}-${{ inputs.image_tag_suffix }} \ - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ env.PROTOCOL_VERSION }}-${{ inputs.image_tag_suffix }} - docker buildx imagetools create \ - --tag europe-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ inputs.image_tag_suffix }} \ - us-docker.pkg.dev/matterlabs-infra/matterlabs-docker/${{ matrix.component }}:2.0-${{ inputs.image_tag_suffix }} diff --git a/.github/workflows/release-test-stage.yml b/.github/workflows/release-test-stage.yml index afd33979234d..cff6c5ee0b3f 100644 --- a/.github/workflows/release-test-stage.yml +++ b/.github/workflows/release-test-stage.yml @@ -121,10 +121,10 @@ jobs: DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - build-gar-prover-fri-gpu-and-circuit-prover-gpu-gar: + build-circuit-prover-gpu-gar: name: Build GAR prover FRI GPU needs: [setup, build-push-prover-images] - uses: ./.github/workflows/build-prover-fri-gpu-gar-and-circuit-prover-gpu-gar.yml + uses: ./.github/workflows/build-circuit-prover-gpu-gar.yml if: needs.changed_files.outputs.prover == 'true' || needs.changed_files.outputs.all == 'true' with: setup_keys_id: ${{ needs.setup.outputs.prover_fri_gpu_key_id }} From 0d40f170d91c9f59de11eaa6e40efab6f7a7d89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Fran=C3=A7a?= Date: Wed, 22 Jan 2025 06:13:50 +0000 Subject: [PATCH 23/52] build(consensus): Bump consensus version (#3512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Bumps version of consensus dependencies to 0.8. New version includes a needed fix for the consensus algorithm and also removes attesters field from Genesis spec (as it was already deprecated before). Tests that use attesters were commented out instead of fixed since most likely the attester feature will be deprecated when we remove L1 batches (with ZK OS). But keeping it for now (not sure if we don't want to reuse the code for something else). --- core/Cargo.lock | 40 +-- core/Cargo.toml | 20 +- core/lib/dal/src/consensus_dal/mod.rs | 1 - core/lib/dal/src/consensus_dal/tests.rs | 269 +++++++++--------- core/node/consensus/src/config.rs | 20 -- core/node/consensus/src/en.rs | 2 +- core/node/consensus/src/mn.rs | 2 +- core/node/consensus/src/registry/mod.rs | 10 +- core/node/consensus/src/registry/tests.rs | 9 +- core/node/consensus/src/storage/connection.rs | 1 - core/node/consensus/src/storage/testonly.rs | 6 +- core/node/consensus/src/tests/attestation.rs | 2 +- core/node/consensus/src/tests/mod.rs | 5 +- prover/Cargo.lock | 28 +- zkstack_cli/Cargo.lock | 24 +- zkstack_cli/Cargo.toml | 10 +- 16 files changed, 213 insertions(+), 236 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index d064b2a3d7bc..ad7915ca2376 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -11427,9 +11427,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8312ab73d3caa55775bd531795b507fa8f76bd9dabfaeb0954fe43e8fc1323b" +checksum = "cec98400a9e8ba02bfd029eacfe7d6fb7b85b8ef00de59d6bb119d29cc9f7442" dependencies = [ "anyhow", "once_cell", @@ -11463,9 +11463,9 @@ dependencies = [ [[package]] name = "zksync_consensus_bft" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6b0944322f30f88cd7fb22f7875435b394a135fc1b479719a18c42d9fb724d" +checksum = "0fa086aeb444d3d0122014fca06959e5c1be507d63596022bd28b8cdcc5cc687" dependencies = [ "anyhow", "async-trait", @@ -11485,9 +11485,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b539960de98df3c3bd27d2d9b97de862027686bbb3bdfc5aaad5b74bb929a1" +checksum = "c04840825dfbe3b9f708d245c87618d5dcf28f29d7b58922971351068a0b8231" dependencies = [ "anyhow", "blst", @@ -11506,9 +11506,9 @@ dependencies = [ [[package]] name = "zksync_consensus_executor" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a75d86368579d5aa59b1baebbdc1aebca7c9234f3e3cba734db7e9bbc4880b0" +checksum = "d6d369ec72851aecdfb24c99ecb50b7c177f0ce7068bb84a17a294a26fb92fab" dependencies = [ "anyhow", "async-trait", @@ -11528,9 +11528,9 @@ dependencies = [ [[package]] name = "zksync_consensus_network" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f73993b7d677dfd4e4f2598dd20906e6a5f3a2168c6cab3a599c056dc5e39a" +checksum = "6a74ed5a9a48d403b48c7ed0dea8cf2cd239e407227657aac27d75d00c3e4bcc" dependencies = [ "anyhow", "async-trait", @@ -11565,9 +11565,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c49949546895a10431b9daec6ec4208ef0917ace006446d304b51f5b234ba462" +checksum = "05498eab1de26869028b5822cfa4490cac625508d427d59668dc73e8162de65f" dependencies = [ "anyhow", "bit-vec", @@ -11587,9 +11587,9 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb0d6a54e7d8d2adeee4ba38662161e9309180ad497299092e5641db9fb1c1e" +checksum = "b20eb99fdd0e171a370214d2b7c99b5d4e8c11b9828a6b5705423bf653849a70" dependencies = [ "anyhow", "async-trait", @@ -11607,9 +11607,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e2a4b056cc5af192a83163c89a6951ee75c098cc5c4a4cdc435f4232d88bd" +checksum = "f2f9fa69ef68e6a1955a1d7b33077103fb6d106b560fec0d599c6de268f5be03" dependencies = [ "anyhow", "rand 0.8.5", @@ -12671,9 +12671,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8986ad796f8e00d8999fee72effba1a21bce40f5f877d681ac9cd89a94834d8" +checksum = "d9032e12528c2466293b206d6edb53b7e900e4a4cc4573e4d075ac2dc00e1b55" dependencies = [ "anyhow", "bit-vec", @@ -12692,9 +12692,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d870b31995e3acb8e47afeb68ebeeffcf6121e70020e65b3d5d31692115d236" +checksum = "7c644fc8ef3c4d343ea42cebd5551e3562933f15dd9b0e68a52c2657603eb0f5" dependencies = [ "anyhow", "heck 0.5.0", diff --git a/core/Cargo.toml b/core/Cargo.toml index 1c071c2839fa..7ed6f2d6b601 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -245,16 +245,16 @@ fflonk = "=0.30.12" zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "457d8a7eea9093af9440662e33e598c13ba41633" } # Consensus dependencies. -zksync_concurrency = "=0.7.0" -zksync_consensus_bft = "=0.7.0" -zksync_consensus_crypto = "=0.7.0" -zksync_consensus_executor = "=0.7.0" -zksync_consensus_network = "=0.7.0" -zksync_consensus_roles = "=0.7.0" -zksync_consensus_storage = "=0.7.0" -zksync_consensus_utils = "=0.7.0" -zksync_protobuf = "=0.7.0" -zksync_protobuf_build = "=0.7.0" +zksync_concurrency = "=0.8.0" +zksync_consensus_bft = "=0.8.0" +zksync_consensus_crypto = "=0.8.0" +zksync_consensus_executor = "=0.8.0" +zksync_consensus_network = "=0.8.0" +zksync_consensus_roles = "=0.8.0" +zksync_consensus_storage = "=0.8.0" +zksync_consensus_utils = "=0.8.0" +zksync_protobuf = "=0.8.0" +zksync_protobuf_build = "=0.8.0" # "Local" dependencies zksync_multivm = { version = "0.1.0", path = "lib/multivm" } diff --git a/core/lib/dal/src/consensus_dal/mod.rs b/core/lib/dal/src/consensus_dal/mod.rs index 7f3bcd1166ad..e6058e86b47d 100644 --- a/core/lib/dal/src/consensus_dal/mod.rs +++ b/core/lib/dal/src/consensus_dal/mod.rs @@ -234,7 +234,6 @@ impl ConsensusDal<'_, '_> { protocol_version: old.genesis.protocol_version, validators: old.genesis.validators.clone(), - attesters: old.genesis.attesters.clone(), leader_selection: old.genesis.leader_selection.clone(), } .with_hash(), diff --git a/core/lib/dal/src/consensus_dal/tests.rs b/core/lib/dal/src/consensus_dal/tests.rs index 694abc8508b6..4b4c5f7b3821 100644 --- a/core/lib/dal/src/consensus_dal/tests.rs +++ b/core/lib/dal/src/consensus_dal/tests.rs @@ -1,17 +1,9 @@ use rand::Rng as _; -use zksync_consensus_roles::{attester, validator}; +use zksync_consensus_roles::validator; use zksync_consensus_storage::ReplicaState; -use zksync_types::{ - block::L1BatchTreeData, - commitment::{L1BatchCommitmentArtifacts, L1BatchCommitmentHash}, - ProtocolVersion, -}; use super::*; -use crate::{ - tests::{create_l1_batch_header, create_l2_block_header}, - ConnectionPool, Core, CoreDal, -}; +use crate::{ConnectionPool, Core, CoreDal}; #[tokio::test] async fn replica_state_read_write() { @@ -52,138 +44,141 @@ async fn replica_state_read_write() { } } -#[tokio::test] -async fn test_batch_certificate() { - let rng = &mut rand::thread_rng(); - let setup = validator::testonly::Setup::new(rng, 3); - let pool = ConnectionPool::::test_pool().await; - let mut conn = pool.connection().await.unwrap(); - let cfg = GlobalConfig { - genesis: setup.genesis.clone(), - registry_address: Some(rng.gen()), - seed_peers: [].into(), - }; - conn.consensus_dal() - .try_update_global_config(&cfg) - .await - .unwrap(); +// NOTE: This test is disabled since we are going to remove L1 batches. Most likely +// we will remove all the attester related code as well, but keeping this until +// we are sure. +// #[tokio::test] +// async fn test_batch_certificate() { +// let rng = &mut rand::thread_rng(); +// let setup = validator::testonly::Setup::new(rng, 3); +// let pool = ConnectionPool::::test_pool().await; +// let mut conn = pool.connection().await.unwrap(); +// let cfg = GlobalConfig { +// genesis: setup.genesis.clone(), +// registry_address: Some(rng.gen()), +// seed_peers: [].into(), +// }; +// conn.consensus_dal() +// .try_update_global_config(&cfg) +// .await +// .unwrap(); - let make_cert = |number: attester::BatchNumber, hash: attester::BatchHash| { - let m = attester::Batch { - genesis: setup.genesis.hash(), - hash, - number, - }; - let mut sigs = attester::MultiSig::default(); - for k in &setup.attester_keys { - sigs.add(k.public(), k.sign_msg(m.clone()).sig); - } - attester::BatchQC { - message: m, - signatures: sigs, - } - }; +// let make_cert = |number: attester::BatchNumber, hash: attester::BatchHash| { +// let m = attester::Batch { +// genesis: setup.genesis.hash(), +// hash, +// number, +// }; +// let mut sigs = attester::MultiSig::default(); +// for k in &setup.attester_keys { +// sigs.add(k.public(), k.sign_msg(m.clone()).sig); +// } +// attester::BatchQC { +// message: m, +// signatures: sigs, +// } +// }; - // Required for inserting l2 blocks - conn.protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); +// // Required for inserting l2 blocks +// conn.protocol_versions_dal() +// .save_protocol_version_with_tx(&ProtocolVersion::default()) +// .await +// .unwrap(); - // Insert some mock L2 blocks and L1 batches - let mut block_number = 0; - let mut batch_number = 0; - for _ in 0..3 { - for _ in 0..3 { - block_number += 1; - let l2_block = create_l2_block_header(block_number); - conn.blocks_dal().insert_l2_block(&l2_block).await.unwrap(); - } - batch_number += 1; - let l1_batch = create_l1_batch_header(batch_number); - conn.blocks_dal() - .insert_mock_l1_batch(&l1_batch) - .await - .unwrap(); - conn.blocks_dal() - .save_l1_batch_tree_data( - l1_batch.number, - &L1BatchTreeData { - hash: rng.gen(), - rollup_last_leaf_index: rng.gen(), - }, - ) - .await - .unwrap(); - conn.blocks_dal() - .save_l1_batch_commitment_artifacts( - l1_batch.number, - &L1BatchCommitmentArtifacts { - commitment_hash: L1BatchCommitmentHash { - pass_through_data: rng.gen(), - aux_output: rng.gen(), - meta_parameters: rng.gen(), - commitment: rng.gen(), - }, - l2_l1_merkle_root: rng.gen(), - compressed_state_diffs: None, - compressed_initial_writes: None, - compressed_repeated_writes: None, - zkporter_is_available: false, - aux_commitments: None, - aggregation_root: rng.gen(), - local_root: rng.gen(), - state_diff_hash: rng.gen(), - }, - ) - .await - .unwrap(); - conn.blocks_dal() - .mark_l2_blocks_as_executed_in_l1_batch(l1_batch.number) - .await - .unwrap(); - } +// // Insert some mock L2 blocks and L1 batches +// let mut block_number = 0; +// let mut batch_number = 0; +// for _ in 0..3 { +// for _ in 0..3 { +// block_number += 1; +// let l2_block = create_l2_block_header(block_number); +// conn.blocks_dal().insert_l2_block(&l2_block).await.unwrap(); +// } +// batch_number += 1; +// let l1_batch = create_l1_batch_header(batch_number); +// conn.blocks_dal() +// .insert_mock_l1_batch(&l1_batch) +// .await +// .unwrap(); +// conn.blocks_dal() +// .save_l1_batch_tree_data( +// l1_batch.number, +// &L1BatchTreeData { +// hash: rng.gen(), +// rollup_last_leaf_index: rng.gen(), +// }, +// ) +// .await +// .unwrap(); +// conn.blocks_dal() +// .save_l1_batch_commitment_artifacts( +// l1_batch.number, +// &L1BatchCommitmentArtifacts { +// commitment_hash: L1BatchCommitmentHash { +// pass_through_data: rng.gen(), +// aux_output: rng.gen(), +// meta_parameters: rng.gen(), +// commitment: rng.gen(), +// }, +// l2_l1_merkle_root: rng.gen(), +// compressed_state_diffs: None, +// compressed_initial_writes: None, +// compressed_repeated_writes: None, +// zkporter_is_available: false, +// aux_commitments: None, +// aggregation_root: rng.gen(), +// local_root: rng.gen(), +// state_diff_hash: rng.gen(), +// }, +// ) +// .await +// .unwrap(); +// conn.blocks_dal() +// .mark_l2_blocks_as_executed_in_l1_batch(l1_batch.number) +// .await +// .unwrap(); +// } - let n = attester::BatchNumber(batch_number.into()); +// let n = attester::BatchNumber(batch_number.into()); - // Insert a batch certificate for the last L1 batch. - let hash = batch_hash(&conn.consensus_dal().batch_info(n).await.unwrap().unwrap()); - let want = make_cert(n, hash); - conn.consensus_dal() - .upsert_attester_committee(n, setup.genesis.attesters.as_ref().unwrap()) - .await - .unwrap(); - conn.consensus_dal() - .insert_batch_certificate(&want) - .await - .unwrap(); +// // Insert a batch certificate for the last L1 batch. +// let hash = batch_hash(&conn.consensus_dal().batch_info(n).await.unwrap().unwrap()); +// let want = make_cert(n, hash); +// conn.consensus_dal() +// .upsert_attester_committee(n, setup.genesis.attesters.as_ref().unwrap()) +// .await +// .unwrap(); +// conn.consensus_dal() +// .insert_batch_certificate(&want) +// .await +// .unwrap(); - // Reinserting a cert should fail. - assert!(conn - .consensus_dal() - .insert_batch_certificate(&make_cert(n, hash)) - .await - .is_err()); +// // Reinserting a cert should fail. +// assert!(conn +// .consensus_dal() +// .insert_batch_certificate(&make_cert(n, hash)) +// .await +// .is_err()); - // Retrieve the latest certificate. - let got_n = conn - .consensus_dal() - .last_batch_certificate_number() - .await - .unwrap() - .unwrap(); - let got = conn - .consensus_dal() - .batch_certificate(got_n) - .await - .unwrap() - .unwrap(); - assert_eq!(got, want); +// // Retrieve the latest certificate. +// let got_n = conn +// .consensus_dal() +// .last_batch_certificate_number() +// .await +// .unwrap() +// .unwrap(); +// let got = conn +// .consensus_dal() +// .batch_certificate(got_n) +// .await +// .unwrap() +// .unwrap(); +// assert_eq!(got, want); - // Try insert batch certificate for non-existing batch - assert!(conn - .consensus_dal() - .insert_batch_certificate(&make_cert(n.next(), rng.gen())) - .await - .is_err()); -} +// // Try insert batch certificate for non-existing batch +// assert!(conn +// .consensus_dal() +// .insert_batch_certificate(&make_cert(n.next(), rng.gen())) +// .await +// .is_err()); +// } diff --git a/core/node/consensus/src/config.rs b/core/node/consensus/src/config.rs index 2cb6045151bd..733500998a30 100644 --- a/core/node/consensus/src/config.rs +++ b/core/node/consensus/src/config.rs @@ -42,7 +42,6 @@ pub(super) struct GenesisSpec { pub(super) chain_id: validator::ChainId, pub(super) protocol_version: validator::ProtocolVersion, pub(super) validators: validator::Committee, - pub(super) attesters: Option, pub(super) leader_selection: validator::LeaderSelectionMode, pub(super) registry_address: Option, pub(super) seed_peers: BTreeMap, @@ -54,7 +53,6 @@ impl GenesisSpec { chain_id: cfg.genesis.chain_id, protocol_version: cfg.genesis.protocol_version, validators: cfg.genesis.validators.clone(), - attesters: cfg.genesis.attesters.clone(), leader_selection: cfg.genesis.leader_selection.clone(), registry_address: cfg.registry_address, seed_peers: cfg.seed_peers.clone(), @@ -75,19 +73,6 @@ impl GenesisSpec { .collect::>() .context("validators")?; - let attesters: Vec<_> = x - .attesters - .iter() - .enumerate() - .map(|(i, v)| { - Ok(attester::WeightedAttester { - key: Text::new(&v.key.0).decode().context("key").context(i)?, - weight: v.weight, - }) - }) - .collect::>() - .context("attesters")?; - Ok(Self { chain_id: validator::ChainId(x.chain_id.as_u64()), protocol_version: validator::ProtocolVersion(x.protocol_version.0), @@ -95,11 +80,6 @@ impl GenesisSpec { Text::new(&x.leader.0).decode().context("leader")?, ), validators: validator::Committee::new(validators).context("validators")?, - attesters: if attesters.is_empty() { - None - } else { - Some(attester::Committee::new(attesters).context("attesters")?) - }, registry_address: x.registry_address, seed_peers: x .seed_peers diff --git a/core/node/consensus/src/en.rs b/core/node/consensus/src/en.rs index 2bddc3280362..ffb580ce1284 100644 --- a/core/node/consensus/src/en.rs +++ b/core/node/consensus/src/en.rs @@ -208,7 +208,7 @@ impl EN { attestation: Arc, ) -> ctx::Result<()> { const POLL_INTERVAL: time::Duration = time::Duration::seconds(5); - let registry = registry::Registry::new(cfg.genesis.clone(), self.pool.clone()).await; + let registry = registry::Registry::new(self.pool.clone()).await; let mut next = attester::BatchNumber(0); loop { let status = loop { diff --git a/core/node/consensus/src/mn.rs b/core/node/consensus/src/mn.rs index a392acfbe5f0..028a635398c6 100644 --- a/core/node/consensus/src/mn.rs +++ b/core/node/consensus/src/mn.rs @@ -118,7 +118,7 @@ async fn run_attestation_controller( attestation: Arc, ) -> ctx::Result<()> { const POLL_INTERVAL: time::Duration = time::Duration::seconds(5); - let registry = registry::Registry::new(cfg.genesis, pool.clone()).await; + let registry = registry::Registry::new(pool.clone()).await; let registry_addr = cfg.registry_address.map(registry::Address::new); let mut next = attester::BatchNumber(0); loop { diff --git a/core/node/consensus/src/registry/mod.rs b/core/node/consensus/src/registry/mod.rs index 74da41309573..235389acda0e 100644 --- a/core/node/consensus/src/registry/mod.rs +++ b/core/node/consensus/src/registry/mod.rs @@ -1,7 +1,7 @@ use anyhow::Context as _; use zksync_concurrency::{ctx, error::Wrap as _}; use zksync_consensus_crypto::ByteFmt; -use zksync_consensus_roles::{attester, validator}; +use zksync_consensus_roles::attester; use crate::{storage::ConnectionPool, vm::VM}; @@ -30,22 +30,20 @@ pub type Address = crate::abi::Address; #[derive(Debug)] pub(crate) struct Registry { contract: abi::ConsensusRegistry, - genesis: validator::Genesis, vm: VM, } impl Registry { - pub async fn new(genesis: validator::Genesis, pool: ConnectionPool) -> Self { + pub async fn new(pool: ConnectionPool) -> Self { Self { contract: abi::ConsensusRegistry::load(), - genesis, vm: VM::new(pool).await, } } /// Attester committee for the given batch. /// It reads committee from the contract. - /// Falls back to committee specified in the genesis. + /// Falls back to empty committee. pub async fn attester_committee_for( &self, ctx: &ctx::Ctx, @@ -57,7 +55,7 @@ impl Registry { return Ok(None); }; let Some(address) = address else { - return Ok(self.genesis.attesters.clone()); + return Ok(None); }; let raw = self .vm diff --git a/core/node/consensus/src/registry/tests.rs b/core/node/consensus/src/registry/tests.rs index 15329077a651..736b6c9b3ac7 100644 --- a/core/node/consensus/src/registry/tests.rs +++ b/core/node/consensus/src/registry/tests.rs @@ -1,6 +1,6 @@ use rand::Rng as _; use zksync_concurrency::{ctx, scope, time}; -use zksync_consensus_roles::{attester, validator::testonly::Setup}; +use zksync_consensus_roles::attester; use zksync_test_contracts::Account; use zksync_types::ProtocolVersionId; @@ -26,21 +26,20 @@ async fn test_attester_committee() { zksync_concurrency::testonly::abort_on_panic(); let ctx = &ctx::test_root(&ctx::RealClock); let rng = &mut ctx.rng(); - let setup = Setup::new(rng, 10); let account = &mut Account::random(); let to_fund = &[account.address]; scope::run!(ctx, |ctx, s| async { let pool = ConnectionPool::test(false, ProtocolVersionId::latest()).await; - let registry = Registry::new(setup.genesis.clone(), pool.clone()).await; + let registry = Registry::new(pool.clone()).await; // If the registry contract address is not specified, - // then the committee from genesis should be returned. + // then an empty committee should be returned. let got = registry .attester_committee_for(ctx, None, attester::BatchNumber(10)) .await .unwrap(); - assert_eq!(setup.genesis.attesters, got); + assert!(got.is_none()); let (mut node, runner) = crate::testonly::StateKeeper::new(ctx, pool.clone()).await?; s.spawn_bg(runner.run_real(ctx, to_fund)); diff --git a/core/node/consensus/src/storage/connection.rs b/core/node/consensus/src/storage/connection.rs index 6ec5794e968d..08fa7996bdc1 100644 --- a/core/node/consensus/src/storage/connection.rs +++ b/core/node/consensus/src/storage/connection.rs @@ -277,7 +277,6 @@ impl<'a> Connection<'a> { first_block: txn.next_block(ctx).await.context("next_block()")?, protocol_version: spec.protocol_version, validators: spec.validators.clone(), - attesters: spec.attesters.clone(), leader_selection: spec.leader_selection.clone(), } .with_hash(), diff --git a/core/node/consensus/src/storage/testonly.rs b/core/node/consensus/src/storage/testonly.rs index 295ae4fc1790..858bca542c33 100644 --- a/core/node/consensus/src/storage/testonly.rs +++ b/core/node/consensus/src/storage/testonly.rs @@ -16,6 +16,7 @@ use crate::registry; impl Connection<'_> { /// Wrapper for `consensus_dal().batch_of_block()`. + #[allow(dead_code)] pub async fn batch_of_block( &mut self, ctx: &ctx::Ctx, @@ -27,6 +28,7 @@ impl Connection<'_> { } /// Wrapper for `consensus_dal().last_batch_certificate_number()`. + #[allow(dead_code)] pub async fn last_batch_certificate_number( &mut self, ctx: &ctx::Ctx, @@ -37,6 +39,7 @@ impl Connection<'_> { } /// Wrapper for `consensus_dal().batch_certificate()`. + #[allow(dead_code)] pub async fn batch_certificate( &mut self, ctx: &ctx::Ctx, @@ -187,6 +190,7 @@ impl ConnectionPool { Ok(blocks) } + #[allow(dead_code)] pub async fn wait_for_batch_certificates_and_verify( &self, ctx: &ctx::Ctx, @@ -217,7 +221,7 @@ impl ConnectionPool { .await .wrap("batch_of_block()")? .context("batch of first_block is missing")?; - let registry = registry::Registry::new(cfg.genesis.clone(), self.clone()).await; + let registry = registry::Registry::new(self.clone()).await; for i in first.0..want_last.0 { let i = attester::BatchNumber(i); let cert = conn diff --git a/core/node/consensus/src/tests/attestation.rs b/core/node/consensus/src/tests/attestation.rs index 6f24fbe65b4c..8e20474f453c 100644 --- a/core/node/consensus/src/tests/attestation.rs +++ b/core/node/consensus/src/tests/attestation.rs @@ -151,7 +151,7 @@ async fn test_multiple_attesters(version: ProtocolVersionId) { tracing::info!("deploy registry with 1 attester"); let attesters: Vec<_> = setup.genesis.attesters.as_ref().unwrap().iter().collect(); - let registry = Registry::new(setup.genesis.clone(), validator_pool.clone()).await; + let registry = Registry::new(validator_pool.clone()).await; let (registry_addr, tx) = registry.deploy(account); cfgs[0] .config diff --git a/core/node/consensus/src/tests/mod.rs b/core/node/consensus/src/tests/mod.rs index c7697ba8480e..c685fdd3223d 100644 --- a/core/node/consensus/src/tests/mod.rs +++ b/core/node/consensus/src/tests/mod.rs @@ -22,7 +22,10 @@ use crate::{ testonly, }; -mod attestation; +// NOTE: These tests are disabled since we are going to remove L1 batches. Most likely +// we will remove all the attester related code as well, but keeping this until +// we are sure. +//mod attestation; const VERSIONS: [ProtocolVersionId; 2] = [ProtocolVersionId::latest(), ProtocolVersionId::next()]; const FROM_SNAPSHOT: [bool; 2] = [true, false]; diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 761c9e398cb4..3d43239a15c6 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -8157,9 +8157,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8312ab73d3caa55775bd531795b507fa8f76bd9dabfaeb0954fe43e8fc1323b" +checksum = "cec98400a9e8ba02bfd029eacfe7d6fb7b85b8ef00de59d6bb119d29cc9f7442" dependencies = [ "anyhow", "once_cell", @@ -8192,9 +8192,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b539960de98df3c3bd27d2d9b97de862027686bbb3bdfc5aaad5b74bb929a1" +checksum = "c04840825dfbe3b9f708d245c87618d5dcf28f29d7b58922971351068a0b8231" dependencies = [ "anyhow", "blst", @@ -8213,9 +8213,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c49949546895a10431b9daec6ec4208ef0917ace006446d304b51f5b234ba462" +checksum = "05498eab1de26869028b5822cfa4490cac625508d427d59668dc73e8162de65f" dependencies = [ "anyhow", "bit-vec 0.6.3", @@ -8235,9 +8235,9 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb0d6a54e7d8d2adeee4ba38662161e9309180ad497299092e5641db9fb1c1e" +checksum = "b20eb99fdd0e171a370214d2b7c99b5d4e8c11b9828a6b5705423bf653849a70" dependencies = [ "anyhow", "async-trait", @@ -8255,9 +8255,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e2a4b056cc5af192a83163c89a6951ee75c098cc5c4a4cdc435f4232d88bd" +checksum = "f2f9fa69ef68e6a1955a1d7b33077103fb6d106b560fec0d599c6de268f5be03" dependencies = [ "anyhow", "rand 0.8.5", @@ -8582,9 +8582,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8986ad796f8e00d8999fee72effba1a21bce40f5f877d681ac9cd89a94834d8" +checksum = "d9032e12528c2466293b206d6edb53b7e900e4a4cc4573e4d075ac2dc00e1b55" dependencies = [ "anyhow", "bit-vec 0.6.3", @@ -8603,9 +8603,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d870b31995e3acb8e47afeb68ebeeffcf6121e70020e65b3d5d31692115d236" +checksum = "7c644fc8ef3c4d343ea42cebd5551e3562933f15dd9b0e68a52c2657603eb0f5" dependencies = [ "anyhow", "heck 0.5.0", diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index cfc16535db50..b21bb44b589a 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -7186,9 +7186,9 @@ dependencies = [ [[package]] name = "zksync_concurrency" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8312ab73d3caa55775bd531795b507fa8f76bd9dabfaeb0954fe43e8fc1323b" +checksum = "cec98400a9e8ba02bfd029eacfe7d6fb7b85b8ef00de59d6bb119d29cc9f7442" dependencies = [ "anyhow", "once_cell", @@ -7219,9 +7219,9 @@ dependencies = [ [[package]] name = "zksync_consensus_crypto" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b539960de98df3c3bd27d2d9b97de862027686bbb3bdfc5aaad5b74bb929a1" +checksum = "c04840825dfbe3b9f708d245c87618d5dcf28f29d7b58922971351068a0b8231" dependencies = [ "anyhow", "blst", @@ -7240,9 +7240,9 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c49949546895a10431b9daec6ec4208ef0917ace006446d304b51f5b234ba462" +checksum = "05498eab1de26869028b5822cfa4490cac625508d427d59668dc73e8162de65f" dependencies = [ "anyhow", "bit-vec", @@ -7262,9 +7262,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e2a4b056cc5af192a83163c89a6951ee75c098cc5c4a4cdc435f4232d88bd" +checksum = "f2f9fa69ef68e6a1955a1d7b33077103fb6d106b560fec0d599c6de268f5be03" dependencies = [ "anyhow", "rand", @@ -7340,9 +7340,9 @@ dependencies = [ [[package]] name = "zksync_protobuf" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8986ad796f8e00d8999fee72effba1a21bce40f5f877d681ac9cd89a94834d8" +checksum = "d9032e12528c2466293b206d6edb53b7e900e4a4cc4573e4d075ac2dc00e1b55" dependencies = [ "anyhow", "bit-vec", @@ -7361,9 +7361,9 @@ dependencies = [ [[package]] name = "zksync_protobuf_build" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d870b31995e3acb8e47afeb68ebeeffcf6121e70020e65b3d5d31692115d236" +checksum = "7c644fc8ef3c4d343ea42cebd5551e3562933f15dd9b0e68a52c2657603eb0f5" dependencies = [ "anyhow", "heck", diff --git a/zkstack_cli/Cargo.toml b/zkstack_cli/Cargo.toml index f3e0d8a9c9d5..79a87b4e1ae2 100644 --- a/zkstack_cli/Cargo.toml +++ b/zkstack_cli/Cargo.toml @@ -35,11 +35,11 @@ zksync_types = { path = "../core/lib/types" } zksync_web3_decl = { path = "../core/lib/web3_decl" } zksync_eth_client = { path = "../core/lib/eth_client" } zksync_contracts = { path = "../core/lib/contracts" } -zksync_consensus_roles = "=0.7.0" -zksync_consensus_crypto = "=0.7.0" -zksync_consensus_utils = "=0.7.0" -zksync_protobuf = "=0.7.0" -zksync_protobuf_build = "=0.7.0" +zksync_consensus_roles = "=0.8.0" +zksync_consensus_crypto = "=0.8.0" +zksync_consensus_utils = "=0.8.0" +zksync_protobuf = "=0.8.0" +zksync_protobuf_build = "=0.8.0" # External dependencies anyhow = "1.0.82" From e25fe6609390968d211179967793253dd7d5ad87 Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Wed, 22 Jan 2025 15:33:47 +0000 Subject: [PATCH 24/52] ci: add unified release please and crates.io publishing (#3508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ CI: * [x] Add unified release-please workflow with automated crates.io publishing * [x] Add separate workflow to publish crates manually if required Rust: * [x] `release-please` is now updating workspace version in Cargo.toml and all crates are re-using the workspace version ## 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`. --- .github/release-please/config.json | 21 +-- .github/workflows/publish-crates.yml | 42 +++++ .../workflows/release-please-cargo-lock.yml | 55 ------ .github/workflows/release-please.yml | 45 ++--- core/Cargo.lock | 156 +++++++++--------- core/Cargo.toml | 132 +++++++-------- core/bin/external_node/Cargo.toml | 2 +- .../Cargo.toml | 2 +- core/bin/selector_generator/Cargo.toml | 2 +- core/bin/snapshots_creator/Cargo.toml | 2 +- .../bin/system-constants-generator/Cargo.toml | 2 +- core/bin/verified_sources_fetcher/Cargo.toml | 2 +- core/bin/zksync_server/Cargo.toml | 2 +- core/bin/zksync_tee_prover/Cargo.toml | 2 +- core/tests/loadnext/Cargo.toml | 2 +- core/tests/vm-benchmark/Cargo.toml | 2 +- etc/nix/tee_prover.nix | 2 +- etc/nix/zksync.nix | 2 +- prover/Cargo.lock | 80 ++++----- prover/Cargo.toml | 53 +++--- prover/rust-toolchain | 3 +- zkstack_cli/Cargo.lock | 36 ++-- zkstack_cli/Cargo.toml | 26 +-- 23 files changed, 332 insertions(+), 341 deletions(-) create mode 100644 .github/workflows/publish-crates.yml delete mode 100644 .github/workflows/release-please-cargo-lock.yml diff --git a/.github/release-please/config.json b/.github/release-please/config.json index 358e249a18bd..28c3583af29e 100644 --- a/.github/release-please/config.json +++ b/.github/release-please/config.json @@ -5,26 +5,27 @@ "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "include-component-in-tag": true, + "release-type": "simple", "packages": { "core": { - "release-type": "simple", - "component": "core", + "component": "core" + }, + "prover": { + "component": "prover", "extra-files": [ { "type": "generic", - "path": "bin/external_node/Cargo.toml" + "path": "Cargo.toml" } ] }, - "prover": { - "release-type": "simple", - "component": "prover" - }, "zkstack_cli": { - "release-type": "simple", "component": "zkstack_cli", - "plugins": [ - "cargo-workspace" + "extra-files": [ + { + "type": "generic", + "path": "Cargo.toml" + } ] } } diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml new file mode 100644 index 000000000000..3c62fbae4f1a --- /dev/null +++ b/.github/workflows/publish-crates.yml @@ -0,0 +1,42 @@ +name: Publish crates + +on: + workflow_dispatch: + inputs: + compontent: + description: 'Component to release. Possible values are: core, prover or zkstack_cli.' + required: true + default: 'zkstack_cli' + run-build: + type: boolean + description: 'Build the workspace before release.' + required: false + default: true + run-tests: + type: boolean + description: 'Run tests before release.' + required: false + default: false + org-owner: + type: string + description: 'Organization to add as owner of the crates.' + required: false + default: 'github:matter-labs:crates-io' + + +jobs: + + publish-crates: + name: Publish to crates.io + runs-on: ubuntu-latest + steps: + - name: Publish crates + uses: matter-labs/zksync-ci-common/.github/actions/publish-crates@v1 + with: + slack_webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} # Slack webhook for notifications + cargo_registry_token: ${{ secrets.CRATES_IO_TOKEN }} # Crates.io token for publishing + workspace_path: ${{ inputs.component }} + org_owner: ${{ inputs.org-owner }} + run_build: ${{ inputs.run-build }} + run_tests: ${{ inputs.run-tests }} + gh_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please-cargo-lock.yml b/.github/workflows/release-please-cargo-lock.yml deleted file mode 100644 index 3b6e615e2960..000000000000 --- a/.github/workflows/release-please-cargo-lock.yml +++ /dev/null @@ -1,55 +0,0 @@ -on: - push: - branches: - - release-please--branches--main--components--core - -name: release-please-update-cargo-lock -jobs: - update_cargo_lock: - # TODO: After migraton switch to CI - runs-on: [matterlabs-default-infra-runners] - - steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 - with: - submodules: "recursive" - persist-credentials: false - - - name: Check last commit - id: condition - run: | - COMMIT=$(git log -1 --pretty=%B) - if [[ "$COMMIT" == "Update Cargo.lock" ]]; then - echo "Cargo.lock is already updated" - echo "::set-output name=skip_steps::true" - else - echo "Cargo.lock should be updated" - echo "::set-output name=skip_steps::false" - fi - - - name: Setup environment - if: steps.condition.outputs.skip_steps != 'true' - run: | - echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV - echo $(pwd)/bin >> $GITHUB_PATH - echo IN_DOCKER=1 >> .env - - - name: Start services - if: steps.condition.outputs.skip_steps != 'true' - run: docker compose up -d zk - - - name: Cargo check - if: steps.condition.outputs.skip_steps != 'true' - run: ci_run cargo check --manifest-path core/Cargo.toml - - - name: Push changes - if: steps.condition.outputs.skip_steps != 'true' - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - run: | - git config --global user.email "zksync-era-bot@users.noreply.github.com" - git config --global user.name "zksync-era-bot" - git remote set-url origin 'https://${{ secrets.RELEASE_TOKEN }}@github.com/matter-labs/zksync-era.git' - git add core/Cargo.lock - git commit -m "Update Cargo.lock" - git push diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 4a8f527f45c6..c1eabbb4c45d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,29 +1,32 @@ +name: Release-please + +# Give permissions to the release-please to open, update PRs +# and commit to PRs the repository to update Cargo.lock +permissions: + contents: write + pull-requests: write + id-token: write + attestations: write + +# Run the workflow on push to the main branch or manually on: push: branches: - main workflow_dispatch: -permissions: - contents: write - pull-requests: write - -name: release-please jobs: - release-please: - runs-on: ubuntu-latest - steps: - - name: Run release-please - id: release - uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4.1.1 - with: - token: ${{ secrets.RELEASE_TOKEN }} - config-file: .github/release-please/config.json - manifest-file: .github/release-please/manifest.json - - name: Send Release Info - if: ${{ steps.release.outputs.releases_created == 'true' }} - uses: matter-labs/format-release-please-for-slack-action@69e6fe9e4ec531b7b5fb0d826f73c190db83cf42 # v2.1.0 - with: - release-please-output: ${{ toJSON(steps.release.outputs) }} - slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_RELEASES }} + # Prepare the release PR with changelog updates and create github releases + release-please: + uses: matter-labs/zksync-ci-common/.github/workflows/release-please.yaml@v1 + secrets: + slack_webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} # Slack webhook for notifications + gh_token: ${{ secrets.GITHUB_TOKEN }} # GitHub token for release-please + with: + config: '.github/release-please/config.json' # Path to the configuration file + manifest: '.github/release-please/manifest.json' # Path to the manifest file + update-cargo-lock: true # Update Cargo.lock file in the release PR + publish-to-crates-io: true # Enable publishing to crates.io + upgrade-dependencies: true # Upgrade cross-workspace dependencies + version-suffix: 'non-semver-compat' # Version suffix for the crates.io release diff --git a/core/Cargo.lock b/core/Cargo.lock index ad7915ca2376..3f9e1ee93561 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1233,7 +1233,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "block_reverter" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -2233,7 +2233,7 @@ dependencies = [ [[package]] name = "custom_genesis_export" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -3485,7 +3485,7 @@ dependencies = [ [[package]] name = "genesis_generator" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -5156,7 +5156,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "loadnext" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -5343,7 +5343,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "merkle_tree_consistency_checker" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -7984,7 +7984,7 @@ dependencies = [ [[package]] name = "selector_generator" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -8644,7 +8644,7 @@ dependencies = [ [[package]] name = "snapshots_creator" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "futures 0.3.31", @@ -9393,7 +9393,7 @@ dependencies = [ [[package]] name = "system-constants-generator" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "codegen", "once_cell", @@ -10373,7 +10373,7 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "verified_sources_fetcher" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "serde_json", @@ -10430,7 +10430,7 @@ dependencies = [ [[package]] name = "vm-benchmark" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "assert_matches", "criterion", @@ -11293,7 +11293,7 @@ dependencies = [ [[package]] name = "zksync_base_token_adjuster" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11315,7 +11315,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -11360,7 +11360,7 @@ dependencies = [ [[package]] name = "zksync_block_reverter" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11384,7 +11384,7 @@ dependencies = [ [[package]] name = "zksync_circuit_breaker" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11398,7 +11398,7 @@ dependencies = [ [[package]] name = "zksync_commitment_generator" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "circuit_encodings", @@ -11446,7 +11446,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -11619,7 +11619,7 @@ dependencies = [ [[package]] name = "zksync_consistency_checker" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11644,7 +11644,7 @@ dependencies = [ [[package]] name = "zksync_contract_verification_server" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "axum 0.7.9", @@ -11663,7 +11663,7 @@ dependencies = [ [[package]] name = "zksync_contract_verifier" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -11680,7 +11680,7 @@ dependencies = [ [[package]] name = "zksync_contract_verifier_lib" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11712,7 +11712,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "bincode", "envy", @@ -11726,7 +11726,7 @@ dependencies = [ [[package]] name = "zksync_core_leftovers" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "ctrlc", @@ -11740,7 +11740,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "blake2 0.10.6", @@ -11768,7 +11768,7 @@ dependencies = [ [[package]] name = "zksync_da_client" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11777,7 +11777,7 @@ dependencies = [ [[package]] name = "zksync_da_clients" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11821,7 +11821,7 @@ dependencies = [ [[package]] name = "zksync_da_dispatcher" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -11838,7 +11838,7 @@ dependencies = [ [[package]] name = "zksync_dal" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "bigdecimal", @@ -11874,7 +11874,7 @@ dependencies = [ [[package]] name = "zksync_db_connection" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11892,7 +11892,7 @@ dependencies = [ [[package]] name = "zksync_env_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "envy", @@ -11904,7 +11904,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "assert_matches", "async-trait", @@ -11926,7 +11926,7 @@ dependencies = [ [[package]] name = "zksync_eth_sender" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11956,7 +11956,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -11967,7 +11967,7 @@ dependencies = [ [[package]] name = "zksync_eth_watch" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-recursion", @@ -11994,7 +11994,7 @@ dependencies = [ [[package]] name = "zksync_external_node" -version = "26.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12048,7 +12048,7 @@ dependencies = [ [[package]] name = "zksync_external_price_api" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12069,7 +12069,7 @@ dependencies = [ [[package]] name = "zksync_external_proof_integration_api" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12115,7 +12115,7 @@ dependencies = [ [[package]] name = "zksync_health_check" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "assert_matches", "async-trait", @@ -12130,7 +12130,7 @@ dependencies = [ [[package]] name = "zksync_house_keeper" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12162,7 +12162,7 @@ dependencies = [ [[package]] name = "zksync_l1_contract_interface" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "circuit_definitions", @@ -12183,7 +12183,7 @@ dependencies = [ [[package]] name = "zksync_logs_bloom_backfill" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "tokio", @@ -12195,7 +12195,7 @@ dependencies = [ [[package]] name = "zksync_mempool" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "tracing", "zksync_types", @@ -12203,7 +12203,7 @@ dependencies = [ [[package]] name = "zksync_merkle_tree" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12232,7 +12232,7 @@ dependencies = [ [[package]] name = "zksync_metadata_calculator" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12266,7 +12266,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "criterion", "once_cell", @@ -12276,7 +12276,7 @@ dependencies = [ [[package]] name = "zksync_multivm" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12308,7 +12308,7 @@ dependencies = [ [[package]] name = "zksync_node_api_server" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12362,7 +12362,7 @@ dependencies = [ [[package]] name = "zksync_node_consensus" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12406,7 +12406,7 @@ dependencies = [ [[package]] name = "zksync_node_db_pruner" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12428,7 +12428,7 @@ dependencies = [ [[package]] name = "zksync_node_fee_model" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12447,7 +12447,7 @@ dependencies = [ [[package]] name = "zksync_node_framework" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12510,7 +12510,7 @@ dependencies = [ [[package]] name = "zksync_node_framework_derive" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "proc-macro2 1.0.92", "quote 1.0.37", @@ -12519,7 +12519,7 @@ dependencies = [ [[package]] name = "zksync_node_genesis" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -12540,7 +12540,7 @@ dependencies = [ [[package]] name = "zksync_node_storage_init" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12562,7 +12562,7 @@ dependencies = [ [[package]] name = "zksync_node_sync" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12597,7 +12597,7 @@ dependencies = [ [[package]] name = "zksync_node_test_utils" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "zksync_contracts", "zksync_dal", @@ -12609,7 +12609,7 @@ dependencies = [ [[package]] name = "zksync_object_store" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12647,7 +12647,7 @@ dependencies = [ [[package]] name = "zksync_proof_data_handler" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "axum 0.7.9", @@ -12709,7 +12709,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -12729,7 +12729,7 @@ dependencies = [ [[package]] name = "zksync_prover_interface" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "bincode", "chrono", @@ -12748,7 +12748,7 @@ dependencies = [ [[package]] name = "zksync_queued_job_processor" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12760,7 +12760,7 @@ dependencies = [ [[package]] name = "zksync_reorg_detector" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12781,7 +12781,7 @@ dependencies = [ [[package]] name = "zksync_server" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -12811,7 +12811,7 @@ dependencies = [ [[package]] name = "zksync_shared_metrics" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "rustc_version 0.4.1", "serde", @@ -12823,7 +12823,7 @@ dependencies = [ [[package]] name = "zksync_snapshots_applier" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12862,7 +12862,7 @@ dependencies = [ [[package]] name = "zksync_state" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12887,7 +12887,7 @@ dependencies = [ [[package]] name = "zksync_state_keeper" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12927,7 +12927,7 @@ dependencies = [ [[package]] name = "zksync_storage" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "num_cpus", "once_cell", @@ -12940,7 +12940,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -12948,7 +12948,7 @@ dependencies = [ [[package]] name = "zksync_tee_prover" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12973,7 +12973,7 @@ dependencies = [ [[package]] name = "zksync_tee_verifier" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -12991,7 +12991,7 @@ dependencies = [ [[package]] name = "zksync_test_contracts" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "ethabi", "foundry-compilers", @@ -13007,7 +13007,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13042,7 +13042,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13058,7 +13058,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -13103,7 +13103,7 @@ dependencies = [ [[package]] name = "zksync_vm_executor" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13121,7 +13121,7 @@ dependencies = [ [[package]] name = "zksync_vm_interface" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13139,7 +13139,7 @@ dependencies = [ [[package]] name = "zksync_vm_runner" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13173,7 +13173,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", diff --git a/core/Cargo.toml b/core/Cargo.toml index 7ed6f2d6b601..3b6ba37c28a6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -91,7 +91,7 @@ inherits = "release" debug = true [workspace.package] -version = "0.1.0" +version = "26.1.0-non-semver-compat" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -257,70 +257,70 @@ zksync_protobuf = "=0.8.0" zksync_protobuf_build = "=0.8.0" # "Local" dependencies -zksync_multivm = { version = "0.1.0", path = "lib/multivm" } -zksync_vlog = { version = "0.1.0", path = "lib/vlog" } -zksync_vm_interface = { version = "0.1.0", path = "lib/vm_interface" } -zksync_vm_executor = { version = "0.1.0", path = "lib/vm_executor" } -zksync_basic_types = { version = "0.1.0", path = "lib/basic_types" } -zksync_circuit_breaker = { version = "0.1.0", path = "lib/circuit_breaker" } -zksync_config = { version = "0.1.0", path = "lib/config" } -zksync_contract_verifier_lib = { version = "0.1.0", path = "lib/contract_verifier" } -zksync_contracts = { version = "0.1.0", path = "lib/contracts" } -zksync_core_leftovers = { version = "0.1.0", path = "lib/zksync_core_leftovers" } -zksync_dal = { version = "0.1.0", path = "lib/dal" } -zksync_db_connection = { version = "0.1.0", path = "lib/db_connection" } -zksync_env_config = { version = "0.1.0", path = "lib/env_config" } -zksync_eth_client = { version = "0.1.0", path = "lib/eth_client" } -zksync_da_client = { version = "0.1.0", path = "lib/da_client" } -zksync_eth_signer = { version = "0.1.0", path = "lib/eth_signer" } -zksync_health_check = { version = "0.1.0", path = "lib/health_check" } -zksync_l1_contract_interface = { version = "0.1.0", path = "lib/l1_contract_interface" } -zksync_mempool = { version = "0.1.0", path = "lib/mempool" } -zksync_merkle_tree = { version = "0.1.0", path = "lib/merkle_tree" } -zksync_bin_metadata = { version = "0.1.0", path = "lib/bin_metadata" } -zksync_mini_merkle_tree = { version = "0.1.0", path = "lib/mini_merkle_tree" } -zksync_object_store = { version = "0.1.0", path = "lib/object_store" } -zksync_protobuf_config = { version = "0.1.0", path = "lib/protobuf_config" } -zksync_prover_interface = { version = "0.1.0", path = "lib/prover_interface" } -zksync_queued_job_processor = { version = "0.1.0", path = "lib/queued_job_processor" } -zksync_snapshots_applier = { version = "0.1.0", path = "lib/snapshots_applier" } -zksync_state = { version = "0.1.0", path = "lib/state" } -zksync_storage = { version = "0.1.0", path = "lib/storage" } -zksync_system_constants = { version = "0.1.0", path = "lib/constants" } -zksync_tee_verifier = { version = "0.1.0", path = "lib/tee_verifier" } -zksync_test_contracts = { version = "0.1.0", path = "lib/test_contracts" } -zksync_types = { version = "0.1.0", path = "lib/types" } -zksync_utils = { version = "0.1.0", path = "lib/utils" } -zksync_web3_decl = { version = "0.1.0", path = "lib/web3_decl" } -zksync_crypto_primitives = { version = "0.1.0", path = "lib/crypto_primitives" } -zksync_external_price_api = { version = "0.1.0", path = "lib/external_price_api" } +zksync_multivm = { version = "=26.1.0-non-semver-compat", path = "lib/multivm" } +zksync_vlog = { version = "=26.1.0-non-semver-compat", path = "lib/vlog" } +zksync_vm_interface = { version = "=26.1.0-non-semver-compat", path = "lib/vm_interface" } +zksync_vm_executor = { version = "=26.1.0-non-semver-compat", path = "lib/vm_executor" } +zksync_basic_types = { version = "=26.1.0-non-semver-compat", path = "lib/basic_types" } +zksync_circuit_breaker = { version = "=26.1.0-non-semver-compat", path = "lib/circuit_breaker" } +zksync_config = { version = "=26.1.0-non-semver-compat", path = "lib/config" } +zksync_contract_verifier_lib = { version = "=26.1.0-non-semver-compat", path = "lib/contract_verifier" } +zksync_contracts = { version = "=26.1.0-non-semver-compat", path = "lib/contracts" } +zksync_core_leftovers = { version = "=26.1.0-non-semver-compat", path = "lib/zksync_core_leftovers" } +zksync_dal = { version = "=26.1.0-non-semver-compat", path = "lib/dal" } +zksync_db_connection = { version = "=26.1.0-non-semver-compat", path = "lib/db_connection" } +zksync_env_config = { version = "=26.1.0-non-semver-compat", path = "lib/env_config" } +zksync_eth_client = { version = "=26.1.0-non-semver-compat", path = "lib/eth_client" } +zksync_da_client = { version = "=26.1.0-non-semver-compat", path = "lib/da_client" } +zksync_eth_signer = { version = "=26.1.0-non-semver-compat", path = "lib/eth_signer" } +zksync_health_check = { version = "=26.1.0-non-semver-compat", path = "lib/health_check" } +zksync_l1_contract_interface = { version = "=26.1.0-non-semver-compat", path = "lib/l1_contract_interface" } +zksync_mempool = { version = "=26.1.0-non-semver-compat", path = "lib/mempool" } +zksync_merkle_tree = { version = "=26.1.0-non-semver-compat", path = "lib/merkle_tree" } +zksync_bin_metadata = { version = "=26.1.0-non-semver-compat", path = "lib/bin_metadata" } +zksync_mini_merkle_tree = { version = "=26.1.0-non-semver-compat", path = "lib/mini_merkle_tree" } +zksync_object_store = { version = "=26.1.0-non-semver-compat", path = "lib/object_store" } +zksync_protobuf_config = { version = "=26.1.0-non-semver-compat", path = "lib/protobuf_config" } +zksync_prover_interface = { version = "=26.1.0-non-semver-compat", path = "lib/prover_interface" } +zksync_queued_job_processor = { version = "=26.1.0-non-semver-compat", path = "lib/queued_job_processor" } +zksync_snapshots_applier = { version = "=26.1.0-non-semver-compat", path = "lib/snapshots_applier" } +zksync_state = { version = "=26.1.0-non-semver-compat", path = "lib/state" } +zksync_storage = { version = "=26.1.0-non-semver-compat", path = "lib/storage" } +zksync_system_constants = { version = "=26.1.0-non-semver-compat", path = "lib/constants" } +zksync_tee_verifier = { version = "=26.1.0-non-semver-compat", path = "lib/tee_verifier" } +zksync_test_contracts = { version = "=26.1.0-non-semver-compat", path = "lib/test_contracts" } +zksync_types = { version = "=26.1.0-non-semver-compat", path = "lib/types" } +zksync_utils = { version = "=26.1.0-non-semver-compat", path = "lib/utils" } +zksync_web3_decl = { version = "=26.1.0-non-semver-compat", path = "lib/web3_decl" } +zksync_crypto_primitives = { version = "=26.1.0-non-semver-compat", path = "lib/crypto_primitives" } +zksync_external_price_api = { version = "=26.1.0-non-semver-compat", path = "lib/external_price_api" } # Framework and components -zksync_node_framework = { version = "0.1.0", path = "node/node_framework" } -zksync_node_framework_derive = { version = "0.1.0", path = "lib/node_framework_derive" } -zksync_eth_watch = { version = "0.1.0", path = "node/eth_watch" } -zksync_shared_metrics = { version = "0.1.0", path = "node/shared_metrics" } -zksync_proof_data_handler = { version = "0.1.0", path = "node/proof_data_handler" } -zksync_block_reverter = { version = "0.1.0", path = "node/block_reverter" } -zksync_commitment_generator = { version = "0.1.0", path = "node/commitment_generator" } -zksync_house_keeper = { version = "0.1.0", path = "node/house_keeper" } -zksync_node_genesis = { version = "0.1.0", path = "node/genesis" } -zksync_da_dispatcher = { version = "0.1.0", path = "node/da_dispatcher" } -zksync_da_clients = { version = "0.1.0", path = "node/da_clients" } -zksync_eth_sender = { version = "0.1.0", path = "node/eth_sender" } -zksync_node_db_pruner = { version = "0.1.0", path = "node/db_pruner" } -zksync_node_fee_model = { version = "0.1.0", path = "node/fee_model" } -zksync_vm_runner = { version = "0.1.0", path = "node/vm_runner" } -zksync_external_proof_integration_api = { version = "0.1.0", path = "node/external_proof_integration_api" } -zksync_node_test_utils = { version = "0.1.0", path = "node/test_utils" } -zksync_state_keeper = { version = "0.1.0", path = "node/state_keeper" } -zksync_reorg_detector = { version = "0.1.0", path = "node/reorg_detector" } -zksync_consistency_checker = { version = "0.1.0", path = "node/consistency_checker" } -zksync_metadata_calculator = { version = "0.1.0", path = "node/metadata_calculator" } -zksync_node_sync = { version = "0.1.0", path = "node/node_sync" } -zksync_node_storage_init = { version = "0.1.0", path = "node/node_storage_init" } -zksync_node_consensus = { version = "0.1.0", path = "node/consensus" } -zksync_contract_verification_server = { version = "0.1.0", path = "node/contract_verification_server" } -zksync_node_api_server = { version = "0.1.0", path = "node/api_server" } -zksync_base_token_adjuster = { version = "0.1.0", path = "node/base_token_adjuster" } -zksync_logs_bloom_backfill = { version = "0.1.0", path = "node/logs_bloom_backfill" } +zksync_node_framework = { version = "=26.1.0-non-semver-compat", path = "node/node_framework" } +zksync_node_framework_derive = { version = "=26.1.0-non-semver-compat", path = "lib/node_framework_derive" } +zksync_eth_watch = { version = "=26.1.0-non-semver-compat", path = "node/eth_watch" } +zksync_shared_metrics = { version = "=26.1.0-non-semver-compat", path = "node/shared_metrics" } +zksync_proof_data_handler = { version = "=26.1.0-non-semver-compat", path = "node/proof_data_handler" } +zksync_block_reverter = { version = "=26.1.0-non-semver-compat", path = "node/block_reverter" } +zksync_commitment_generator = { version = "=26.1.0-non-semver-compat", path = "node/commitment_generator" } +zksync_house_keeper = { version = "=26.1.0-non-semver-compat", path = "node/house_keeper" } +zksync_node_genesis = { version = "=26.1.0-non-semver-compat", path = "node/genesis" } +zksync_da_dispatcher = { version = "=26.1.0-non-semver-compat", path = "node/da_dispatcher" } +zksync_da_clients = { version = "=26.1.0-non-semver-compat", path = "node/da_clients" } +zksync_eth_sender = { version = "=26.1.0-non-semver-compat", path = "node/eth_sender" } +zksync_node_db_pruner = { version = "=26.1.0-non-semver-compat", path = "node/db_pruner" } +zksync_node_fee_model = { version = "=26.1.0-non-semver-compat", path = "node/fee_model" } +zksync_vm_runner = { version = "=26.1.0-non-semver-compat", path = "node/vm_runner" } +zksync_external_proof_integration_api = { version = "=26.1.0-non-semver-compat", path = "node/external_proof_integration_api" } +zksync_node_test_utils = { version = "=26.1.0-non-semver-compat", path = "node/test_utils" } +zksync_state_keeper = { version = "=26.1.0-non-semver-compat", path = "node/state_keeper" } +zksync_reorg_detector = { version = "=26.1.0-non-semver-compat", path = "node/reorg_detector" } +zksync_consistency_checker = { version = "=26.1.0-non-semver-compat", path = "node/consistency_checker" } +zksync_metadata_calculator = { version = "=26.1.0-non-semver-compat", path = "node/metadata_calculator" } +zksync_node_sync = { version = "=26.1.0-non-semver-compat", path = "node/node_sync" } +zksync_node_storage_init = { version = "=26.1.0-non-semver-compat", path = "node/node_storage_init" } +zksync_node_consensus = { version = "=26.1.0-non-semver-compat", path = "node/consensus" } +zksync_contract_verification_server = { version = "=26.1.0-non-semver-compat", path = "node/contract_verification_server" } +zksync_node_api_server = { version = "=26.1.0-non-semver-compat", path = "node/api_server" } +zksync_base_token_adjuster = { version = "=26.1.0-non-semver-compat", path = "node/base_token_adjuster" } +zksync_logs_bloom_backfill = { version = "=26.1.0-non-semver-compat", path = "node/logs_bloom_backfill" } diff --git a/core/bin/external_node/Cargo.toml b/core/bin/external_node/Cargo.toml index f9e494a62780..cb75a20c3ed7 100644 --- a/core/bin/external_node/Cargo.toml +++ b/core/bin/external_node/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zksync_external_node" description = "Non-validator ZKsync node" -version = "26.1.0" # x-release-please-version +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/merkle_tree_consistency_checker/Cargo.toml b/core/bin/merkle_tree_consistency_checker/Cargo.toml index eb7dcd81a0dc..f915f321f139 100644 --- a/core/bin/merkle_tree_consistency_checker/Cargo.toml +++ b/core/bin/merkle_tree_consistency_checker/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "merkle_tree_consistency_checker" description = "Tool to verify consistency of ZKsync Merkle Tree" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/selector_generator/Cargo.toml b/core/bin/selector_generator/Cargo.toml index b3425c11b4ec..28b3983605b3 100644 --- a/core/bin/selector_generator/Cargo.toml +++ b/core/bin/selector_generator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "selector_generator" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/snapshots_creator/Cargo.toml b/core/bin/snapshots_creator/Cargo.toml index 5a36c646e88e..aa2dde097240 100644 --- a/core/bin/snapshots_creator/Cargo.toml +++ b/core/bin/snapshots_creator/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "snapshots_creator" description = "Tool to create ZKsync state snapshots" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/system-constants-generator/Cargo.toml b/core/bin/system-constants-generator/Cargo.toml index 7177d29ca743..d3b600ba258f 100644 --- a/core/bin/system-constants-generator/Cargo.toml +++ b/core/bin/system-constants-generator/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "system-constants-generator" description = "Tool for generating JSON files with the system constants for L1/L2 contracts" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/verified_sources_fetcher/Cargo.toml b/core/bin/verified_sources_fetcher/Cargo.toml index 5fa90590ed5f..b143cafdbaee 100644 --- a/core/bin/verified_sources_fetcher/Cargo.toml +++ b/core/bin/verified_sources_fetcher/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "verified_sources_fetcher" description = "Tool to fetch verified contract sources" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/zksync_server/Cargo.toml b/core/bin/zksync_server/Cargo.toml index 4cf028be8210..e5eeeb0c79a9 100644 --- a/core/bin/zksync_server/Cargo.toml +++ b/core/bin/zksync_server/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zksync_server" description = "ZKsync validator/sequencer node" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/bin/zksync_tee_prover/Cargo.toml b/core/bin/zksync_tee_prover/Cargo.toml index b853da348ee0..303ad30cf2ac 100644 --- a/core/bin/zksync_tee_prover/Cargo.toml +++ b/core/bin/zksync_tee_prover/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zksync_tee_prover" description = "ZKsync TEE prover" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/tests/loadnext/Cargo.toml b/core/tests/loadnext/Cargo.toml index 91f987035acf..32957f0372ac 100644 --- a/core/tests/loadnext/Cargo.toml +++ b/core/tests/loadnext/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "loadnext" -version = "0.1.0" +version.workspace = true edition.workspace = true authors.workspace = true homepage.workspace = true diff --git a/core/tests/vm-benchmark/Cargo.toml b/core/tests/vm-benchmark/Cargo.toml index eb4a5a239252..f7c1bf880bad 100644 --- a/core/tests/vm-benchmark/Cargo.toml +++ b/core/tests/vm-benchmark/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vm-benchmark" -version = "0.1.0" +version.workspace = true edition.workspace = true license.workspace = true publish = false diff --git a/etc/nix/tee_prover.nix b/etc/nix/tee_prover.nix index 55545d1bb8e4..5811297ce854 100644 --- a/etc/nix/tee_prover.nix +++ b/etc/nix/tee_prover.nix @@ -7,7 +7,7 @@ let in craneLib.buildPackage (commonArgs // { inherit pname; - version = (builtins.fromTOML (builtins.readFile ../../core/bin/zksync_tee_prover/Cargo.toml)).package.version; + version = (builtins.fromTOML (builtins.readFile ../../core/Cargo.toml)).workspace.package.version; inherit cargoExtraArgs; cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { diff --git a/etc/nix/zksync.nix b/etc/nix/zksync.nix index 1ecac58b5d91..16d452c01bfd 100644 --- a/etc/nix/zksync.nix +++ b/etc/nix/zksync.nix @@ -3,7 +3,7 @@ }: craneLib.buildPackage (commonArgs // { pname = "zksync"; - version = (builtins.fromTOML (builtins.readFile ../../core/bin/zksync_tee_prover/Cargo.toml)).package.version; + version = (builtins.fromTOML (builtins.readFile ../../core/Cargo.toml)).workspace.package.version; cargoExtraArgs = "--all"; cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 3d43239a15c6..a9a8d7e71c54 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -4705,7 +4705,7 @@ dependencies = [ [[package]] name = "prover_cli" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "assert_cmd", @@ -4738,7 +4738,7 @@ dependencies = [ [[package]] name = "prover_version" -version = "0.1.0" +version = "17.1.1" dependencies = [ "zksync_prover_fri_types", ] @@ -8063,7 +8063,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -8107,7 +8107,7 @@ dependencies = [ [[package]] name = "zksync_circuit_prover" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8137,7 +8137,7 @@ dependencies = [ [[package]] name = "zksync_circuit_prover_service" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8176,7 +8176,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -8267,7 +8267,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "envy", "hex", @@ -8280,7 +8280,7 @@ dependencies = [ [[package]] name = "zksync_core_leftovers" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "ctrlc", @@ -8294,7 +8294,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "blake2 0.10.6", @@ -8322,7 +8322,7 @@ dependencies = [ [[package]] name = "zksync_dal" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "bigdecimal", @@ -8357,7 +8357,7 @@ dependencies = [ [[package]] name = "zksync_db_connection" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -8373,7 +8373,7 @@ dependencies = [ [[package]] name = "zksync_env_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "envy", @@ -8384,7 +8384,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "async-trait", "jsonrpsee", @@ -8401,7 +8401,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -8457,7 +8457,7 @@ dependencies = [ [[package]] name = "zksync_l1_contract_interface" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "circuit_definitions", @@ -8474,7 +8474,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -8483,7 +8483,7 @@ dependencies = [ [[package]] name = "zksync_multivm" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "circuit_sequencer_api", @@ -8509,7 +8509,7 @@ dependencies = [ [[package]] name = "zksync_object_store" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8545,7 +8545,7 @@ dependencies = [ [[package]] name = "zksync_proof_fri_compressor" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8620,7 +8620,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -8640,7 +8640,7 @@ dependencies = [ [[package]] name = "zksync_prover_autoscaler" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8677,7 +8677,7 @@ dependencies = [ [[package]] name = "zksync_prover_dal" -version = "0.1.0" +version = "17.1.1" dependencies = [ "sqlx", "strum", @@ -8687,7 +8687,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8721,7 +8721,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri_gateway" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8747,7 +8747,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri_types" -version = "0.1.0" +version = "17.1.1" dependencies = [ "circuit_definitions", "serde", @@ -8757,7 +8757,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri_utils" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "regex", @@ -8775,7 +8775,7 @@ dependencies = [ [[package]] name = "zksync_prover_interface" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "chrono", "circuit_definitions", @@ -8791,7 +8791,7 @@ dependencies = [ [[package]] name = "zksync_prover_job_monitor" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8813,7 +8813,7 @@ dependencies = [ [[package]] name = "zksync_prover_job_processor" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -8828,7 +8828,7 @@ dependencies = [ [[package]] name = "zksync_prover_keystore" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "bincode", @@ -8855,7 +8855,7 @@ dependencies = [ [[package]] name = "zksync_queued_job_processor" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8884,7 +8884,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -8892,7 +8892,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8924,7 +8924,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "futures 0.3.31", @@ -8939,7 +8939,7 @@ dependencies = [ [[package]] name = "zksync_vk_setup_data_generator_server_fri" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "bincode", @@ -8963,7 +8963,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -9008,7 +9008,7 @@ dependencies = [ [[package]] name = "zksync_vm_interface" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -9024,7 +9024,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -9045,7 +9045,7 @@ dependencies = [ [[package]] name = "zksync_witness_generator" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", @@ -9083,7 +9083,7 @@ dependencies = [ [[package]] name = "zksync_witness_vector_generator" -version = "0.1.0" +version = "17.1.1" dependencies = [ "anyhow", "async-trait", diff --git a/prover/Cargo.toml b/prover/Cargo.toml index c1d006f2a759..6c249b7094ae 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/bin/*", "crates/lib/*"] resolver = "2" [workspace.package] -version = "0.1.0" +version = "17.1.1" # x-release-please-version edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -82,34 +82,33 @@ shivini = "=0.152.10" boojum-cuda = "=0.152.10" # Core workspace dependencies -zksync_multivm = { path = "../core/lib/multivm", version = "0.1.0" } -zksync_vlog = { path = "../core/lib/vlog" } -zksync_basic_types = { path = "../core/lib/basic_types" } -zksync_config = { path = "../core/lib/config" } -zksync_dal = { path = "../core/lib/dal" } -zksync_db_connection = { path = "../core/lib/db_connection" } -zksync_env_config = { path = "../core/lib/env_config" } -zksync_object_store = { path = "../core/lib/object_store" } -zksync_prover_interface = { path = "../core/lib/prover_interface" } -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_eth_client = { path = "../core/lib/eth_client" } -zksync_contracts = { path = "../core/lib/contracts" } -zksync_core_leftovers = { path = "../core/lib/zksync_core_leftovers" } -zksync_periodic_job = { path = "../core/lib/periodic_job" } -zksync_protobuf_config = { path = "../core/lib/protobuf_config" } +zksync_multivm = { version = "=26.1.0-non-semver-compat", path = "../core/lib/multivm" } +zksync_vlog = { version = "=26.1.0-non-semver-compat", path = "../core/lib/vlog" } +zksync_basic_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/basic_types" } +zksync_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/config" } +zksync_dal = { version = "=26.1.0-non-semver-compat", path = "../core/lib/dal" } +zksync_db_connection = { version = "=26.1.0-non-semver-compat", path = "../core/lib/db_connection" } +zksync_env_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/env_config" } +zksync_object_store = { version = "=26.1.0-non-semver-compat", path = "../core/lib/object_store" } +zksync_prover_interface = { version = "=26.1.0-non-semver-compat", path = "../core/lib/prover_interface" } +zksync_queued_job_processor = { version = "=26.1.0-non-semver-compat", path = "../core/lib/queued_job_processor" } +zksync_system_constants = { version = "=26.1.0-non-semver-compat", path = "../core/lib/constants" } +zksync_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/types" } +zksync_utils = { version = "=26.1.0-non-semver-compat", path = "../core/lib/utils" } +zksync_eth_client = { version = "=26.1.0-non-semver-compat", path = "../core/lib/eth_client" } +zksync_contracts = { version = "=26.1.0-non-semver-compat", path = "../core/lib/contracts" } +zksync_core_leftovers = { version = "=26.1.0-non-semver-compat", path = "../core/lib/zksync_core_leftovers" } +zksync_protobuf_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/protobuf_config" } # Prover workspace dependencies -zksync_prover_dal = { path = "crates/lib/prover_dal" } -zksync_prover_fri_types = { path = "crates/lib/prover_fri_types" } -zksync_prover_fri_utils = { path = "crates/lib/prover_fri_utils" } -zksync_prover_keystore = { path = "crates/lib/keystore" } -zksync_vk_setup_data_generator_server_fri = { path = "crates/bin/vk_setup_data_generator_server_fri" } -zksync_prover_job_processor = { path = "crates/lib/prover_job_processor" } -zksync_circuit_prover_service = { path = "crates/lib/circuit_prover_service" } -zksync_prover_job_monitor = { path = "crates/bin/prover_job_monitor" } +zksync_prover_dal = { version = "17.1.1", path = "crates/lib/prover_dal" } +zksync_prover_fri_types = { version = "17.1.1", path = "crates/lib/prover_fri_types" } +zksync_prover_fri_utils = { version = "17.1.1", path = "crates/lib/prover_fri_utils" } +zksync_prover_keystore = { version = "17.1.1", path = "crates/lib/keystore" } +zksync_vk_setup_data_generator_server_fri = { version = "17.1.1", path = "crates/bin/vk_setup_data_generator_server_fri" } +zksync_prover_job_processor = { version = "17.1.1", path = "crates/lib/prover_job_processor" } +zksync_circuit_prover_service = { version = "17.1.1", path = "crates/lib/circuit_prover_service" } +zksync_prover_job_monitor = { version = "17.1.1", path = "crates/bin/prover_job_monitor" } # for `perf` profiling [profile.perf] diff --git a/prover/rust-toolchain b/prover/rust-toolchain index 03c040b91f1f..bc5d1d6bbd8e 100644 --- a/prover/rust-toolchain +++ b/prover/rust-toolchain @@ -1 +1,2 @@ -nightly-2024-08-01 +[toolchain] +channel = "nightly-2024-08-01" diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index b21bb44b589a..bbdb50159076 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -7045,7 +7045,7 @@ dependencies = [ [[package]] name = "zkstack" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anyhow", "chrono", @@ -7093,7 +7093,7 @@ dependencies = [ [[package]] name = "zkstack_cli_common" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anyhow", "async-trait", @@ -7122,7 +7122,7 @@ dependencies = [ [[package]] name = "zkstack_cli_config" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anyhow", "clap", @@ -7146,14 +7146,14 @@ dependencies = [ [[package]] name = "zkstack_cli_git_version_macro" -version = "0.1.0" +version = "0.1.2" dependencies = [ "chrono", ] [[package]] name = "zkstack_cli_types" -version = "0.1.0" +version = "0.1.2" dependencies = [ "clap", "ethers", @@ -7165,7 +7165,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -7205,7 +7205,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "rand", @@ -7274,7 +7274,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "envy", "hex", @@ -7287,7 +7287,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "blake2", @@ -7303,7 +7303,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "async-trait", "jsonrpsee", @@ -7320,7 +7320,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -7331,7 +7331,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -7378,7 +7378,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -7398,7 +7398,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -7406,7 +7406,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -7438,7 +7438,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "futures", @@ -7453,7 +7453,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -7478,7 +7478,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "0.1.0" +version = "26.1.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", diff --git a/zkstack_cli/Cargo.toml b/zkstack_cli/Cargo.toml index 79a87b4e1ae2..0d726ccc3600 100644 --- a/zkstack_cli/Cargo.toml +++ b/zkstack_cli/Cargo.toml @@ -9,7 +9,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.0" +version = "0.1.2" # x-release-please-version edition = "2021" homepage = "https://zksync.io/" license = "MIT OR Apache-2.0" @@ -21,20 +21,20 @@ keywords = ["zk", "cryptography", "blockchain", "ZKStack", "ZKsync"] [workspace.dependencies] # Local dependencies -zkstack_cli_common = { path = "crates/common" } -zkstack_cli_config = { path = "crates/config" } -zkstack_cli_types = { path = "crates/types" } -zkstack_cli_git_version_macro = { path = "crates/git_version_macro" } +zkstack_cli_common = { version = "0.1.2", path = "crates/common" } +zkstack_cli_config = { version = "0.1.2", path = "crates/config" } +zkstack_cli_types = { version = "0.1.2", path = "crates/types" } +zkstack_cli_git_version_macro = { version = "0.1.2", path = "crates/git_version_macro" } # ZkSync deps -zksync_config = { path = "../core/lib/config" } -zksync_protobuf_config = { path = "../core/lib/protobuf_config" } -zksync_basic_types = { path = "../core/lib/basic_types" } -zksync_system_constants = { path = "../core/lib/constants" } -zksync_types = { path = "../core/lib/types" } -zksync_web3_decl = { path = "../core/lib/web3_decl" } -zksync_eth_client = { path = "../core/lib/eth_client" } -zksync_contracts = { path = "../core/lib/contracts" } +zksync_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/config" } +zksync_protobuf_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/protobuf_config" } +zksync_basic_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/basic_types" } +zksync_system_constants = { version = "=26.1.0-non-semver-compat", path = "../core/lib/constants" } +zksync_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/types" } +zksync_web3_decl = { version = "=26.1.0-non-semver-compat", path = "../core/lib/web3_decl" } +zksync_eth_client = { version = "=26.1.0-non-semver-compat", path = "../core/lib/eth_client" } +zksync_contracts = { version = "=26.1.0-non-semver-compat", path = "../core/lib/contracts" } zksync_consensus_roles = "=0.8.0" zksync_consensus_crypto = "=0.8.0" zksync_consensus_utils = "=0.8.0" From 516e5210ed70b25a15a68a58c8065331aab542e0 Mon Sep 17 00:00:00 2001 From: Artem Fomiuk <88630083+Artemka374@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:05:11 +0200 Subject: [PATCH 25/52] fix: JSON proof serialization (#3514) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Add `#[serde(untagged)]` for L1BatchProofForL1 to ensure backwards compatibility. ## 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/prover_interface/src/outputs.rs | 1 + core/lib/prover_interface/tests/job_serialization.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/lib/prover_interface/src/outputs.rs b/core/lib/prover_interface/src/outputs.rs index b536c39778a5..1c3a613628d3 100644 --- a/core/lib/prover_interface/src/outputs.rs +++ b/core/lib/prover_interface/src/outputs.rs @@ -13,6 +13,7 @@ use zksync_types::{protocol_version::ProtocolSemanticVersion, tee_types::TeeType /// A "final" ZK proof that can be sent to the L1 contract. #[derive(Clone, Serialize, Deserialize)] +#[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum L1BatchProofForL1 { Fflonk(FflonkL1BatchProofForL1), diff --git a/core/lib/prover_interface/tests/job_serialization.rs b/core/lib/prover_interface/tests/job_serialization.rs index cc78a9acf262..f28a8d94bf0c 100644 --- a/core/lib/prover_interface/tests/job_serialization.rs +++ b/core/lib/prover_interface/tests/job_serialization.rs @@ -102,8 +102,7 @@ fn test_proof_request_serialization() { let encoded_obj = serde_json::to_string(&proof).unwrap(); let encoded_json = r#"{ "Proof": { - "Plonk": { - "aggregation_result_coords": [ + "aggregation_result_coords": [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], @@ -159,7 +158,6 @@ fn test_proof_request_serialization() { }, "protocol_version": "0.25.10" } - } }"#; let decoded_obj: SubmitProofRequest = serde_json::from_str(&encoded_obj).unwrap(); let decoded_json: SubmitProofRequest = serde_json::from_str(encoded_json).unwrap(); From 4fd5944e9ea34f5662947429e9280eeeba60faf8 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Wed, 22 Jan 2025 20:29:38 +0200 Subject: [PATCH 26/52] ci: explicit nonces for upgrade test (#3517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Explicit nonces for upgrade test ## Why ❔ Test is flaky, sometimes incorrect nonce is used ## 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/tests/upgrade-test/tests/upgrade.test.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/core/tests/upgrade-test/tests/upgrade.test.ts b/core/tests/upgrade-test/tests/upgrade.test.ts index 5a1902ec8671..b593e5ad6677 100644 --- a/core/tests/upgrade-test/tests/upgrade.test.ts +++ b/core/tests/upgrade-test/tests/upgrade.test.ts @@ -268,9 +268,10 @@ describe('Upgrade test', function () { bootloaderHash = ethers.hexlify(zksync.utils.hashBytecode(bootloaderCode)); defaultAccountHash = ethers.hexlify(zksync.utils.hashBytecode(defaultAACode)); - await publishBytecode(tester.ethWallet, bytecodeSupplier, bootloaderCode); - await publishBytecode(tester.ethWallet, bytecodeSupplier, defaultAACode); - await publishBytecode(tester.ethWallet, bytecodeSupplier, forceDeployBytecode); + let nonce = await tester.ethWallet.getNonce(); + nonce += await publishBytecode(tester.ethWallet, bytecodeSupplier, bootloaderCode, nonce); + nonce += await publishBytecode(tester.ethWallet, bytecodeSupplier, defaultAACode, nonce); + await publishBytecode(tester.ethWallet, bytecodeSupplier, forceDeployBytecode, nonce); }); step('Schedule governance call', async () => { @@ -523,7 +524,12 @@ function readCode(newPath: string, legacyPath: string): string { } } -async function publishBytecode(wallet: ethers.Wallet, bytecodeSupplierAddr: string, bytecode: string) { +async function publishBytecode( + wallet: ethers.Wallet, + bytecodeSupplierAddr: string, + bytecode: string, + nonce: number +): Promise { const hash = zksync.utils.hashBytecode(bytecode); const abi = [ 'function publishBytecode(bytes calldata _bytecode) public', @@ -533,8 +539,11 @@ async function publishBytecode(wallet: ethers.Wallet, bytecodeSupplierAddr: stri const contract = new ethers.Contract(bytecodeSupplierAddr, abi, wallet); const block = await contract.publishingBlock(hash); if (block == BigInt(0)) { - await (await contract.publishBytecode(bytecode)).wait(); + const tx = await contract.publishBytecode(bytecode, { nonce }); + await tx.wait(); + return 1; } + return 0; } async function checkedRandomTransfer(sender: zksync.Wallet, amount: bigint): Promise { From 01a3d7a734a2352072dd6d902e1f49b3ab31addc Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 23 Jan 2025 19:45:11 +1100 Subject: [PATCH 27/52] ci: fix nonce reuse (#3520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ In a very unlikely scenario timeouted tx can still succeed in the short window while we are resubmitting it with updated gas. This PR handles this gracefully + another case when nonce might have been used asynchronously by someone else. ## Why ❔ Good ol' flakiness ## 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/tests/ts-integration/src/helpers.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/tests/ts-integration/src/helpers.ts b/core/tests/ts-integration/src/helpers.ts index 758884e2be99..58b698681388 100644 --- a/core/tests/ts-integration/src/helpers.ts +++ b/core/tests/ts-integration/src/helpers.ts @@ -104,6 +104,22 @@ export async function waitForNewL1Batch(wallet: zksync.Wallet): Promisee.message.match(/nonce too low/)) { + if (!txResponse) { + // Our transaction was never accepted to the mempool with this nonce so it must have been used by another transaction. + return wallet.getNonce().then((newNonce) => { + console.log( + `Transaction's nonce is too low, updating from ${nonce} to ${newNonce} (attempt ${i + 1}/${MAX_ATTEMPTS})` + ); + nonce = newNonce; + return null; + }); + } else { + console.log( + `Transaction's nonce is too low, likely previous attempt succeeded, waiting longer (attempt ${i + 1}/${MAX_ATTEMPTS})` + ); + return txResponse; + } } else { return Promise.reject(e); } From c916797d49d636c9e642264786d4124ebd338ec3 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 23 Jan 2025 11:00:26 +0200 Subject: [PATCH 28/52] fix(en): Fix race condition in EN storage initialization (#3515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Reworks `NodeStorageInitializer::is_chain_tip_correct()` so that it performs the minimum amount of work possible, i.e. detects whether the latest L1 batch / L2 block diverge or not. ## Why ❔ EN storage initialization is prone to a data race: the "is storage initialized" check calls `NodeStorageInitializer::is_chain_tip_correct()`, which internally performs the entire iteration of the reorg detector (in particular, binary search for the first diverged block). This can lead to a data race with block revert logic, which may be executed concurrently. This data race was observed on the revert integration tests. ## 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`. --- .../src/external_node/revert.rs | 6 ++ core/node/node_storage_init/src/lib.rs | 5 +- core/node/node_storage_init/src/traits.rs | 3 + core/node/reorg_detector/src/lib.rs | 86 ++++++++++++++----- core/node/reorg_detector/src/tests.rs | 10 +++ 5 files changed, 86 insertions(+), 24 deletions(-) diff --git a/core/node/node_storage_init/src/external_node/revert.rs b/core/node/node_storage_init/src/external_node/revert.rs index 86d137c6b660..db06a4492bb7 100644 --- a/core/node/node_storage_init/src/external_node/revert.rs +++ b/core/node/node_storage_init/src/external_node/revert.rs @@ -34,6 +34,12 @@ impl RevertStorage for ExternalNodeReverter { Ok(()) } + async fn is_reorg_needed(&self, stop_receiver: watch::Receiver) -> anyhow::Result { + ReorgDetector::new(self.client.clone(), self.pool.clone()) + .check_reorg_presence(stop_receiver) + .await + } + async fn last_correct_batch_for_reorg( &self, stop_receiver: watch::Receiver, diff --git a/core/node/node_storage_init/src/lib.rs b/core/node/node_storage_init/src/lib.rs index 10b0131908ca..a8b72b769a18 100644 --- a/core/node/node_storage_init/src/lib.rs +++ b/core/node/node_storage_init/src/lib.rs @@ -182,10 +182,7 @@ impl NodeStorageInitializer { ) -> anyhow::Result { // May be `true` if stop signal is received, but the node will shut down without launching any tasks anyway. let initialized = if let Some(reverter) = &self.strategy.block_reverter { - reverter - .last_correct_batch_for_reorg(stop_receiver) - .await? - .is_none() + !reverter.is_reorg_needed(stop_receiver).await? } else { true }; diff --git a/core/node/node_storage_init/src/traits.rs b/core/node/node_storage_init/src/traits.rs index 3b6467764d97..d28b0226d845 100644 --- a/core/node/node_storage_init/src/traits.rs +++ b/core/node/node_storage_init/src/traits.rs @@ -18,6 +18,9 @@ pub trait InitializeStorage: fmt::Debug + Send + Sync + 'static { /// This trait assumes that for any invalid state there exists a batch number to which the storage can be rolled back. #[async_trait::async_trait] pub trait RevertStorage: fmt::Debug + Send + Sync + 'static { + /// Checks whether a reorg is needed for the storage. + async fn is_reorg_needed(&self, stop_receiver: watch::Receiver) -> anyhow::Result; + /// Checks if the storage is invalid state and has to be rolled back. async fn last_correct_batch_for_reorg( &self, diff --git a/core/node/reorg_detector/src/lib.rs b/core/node/reorg_detector/src/lib.rs index d1954ca4b74b..ec5b505d7803 100644 --- a/core/node/reorg_detector/src/lib.rs +++ b/core/node/reorg_detector/src/lib.rs @@ -266,26 +266,32 @@ impl ReorgDetector { &self.health_check } - async fn check_consistency(&mut self) -> Result<(), Error> { + async fn find_last_diverged_batch(&mut self) -> Result, HashMatchError> { let mut storage = self.pool.connection().await?; - let Some(local_l1_batch) = storage + // Create a readonly transaction to get a consistent view of the storage. + let mut storage_tx = storage + .transaction_builder()? + .set_readonly() + .build() + .await?; + let Some(local_l1_batch) = storage_tx .blocks_dal() .get_last_l1_batch_number_with_tree_data() .await? else { - return Ok(()); + return Ok(None); }; - let Some(local_l2_block) = storage.blocks_dal().get_sealed_l2_block_number().await? else { - return Ok(()); + let Some(local_l2_block) = storage_tx.blocks_dal().get_sealed_l2_block_number().await? + else { + return Ok(None); }; + drop(storage_tx); drop(storage); let remote_l1_batch = self.client.sealed_l1_batch_number().await?; let remote_l2_block = self.client.sealed_l2_block_number().await?; - let checked_l1_batch = local_l1_batch.min(remote_l1_batch); let checked_l2_block = local_l2_block.min(remote_l2_block); - let root_hashes_match = self.root_hashes_match(checked_l1_batch).await?; let l2_block_hashes_match = self.l2_block_hashes_match(checked_l2_block).await?; @@ -295,13 +301,21 @@ impl ReorgDetector { // In other cases either there is only a height mismatch which means that one of // the nodes needs to do catching up; however, it is not certain that there is actually // a re-org taking place. - if root_hashes_match && l2_block_hashes_match { + Ok(if root_hashes_match && l2_block_hashes_match { self.event_handler .update_correct_block(checked_l2_block, checked_l1_batch); + None + } else { + let diverged_l1_batch = checked_l1_batch + (root_hashes_match as u32); + self.event_handler.report_divergence(diverged_l1_batch); + Some(diverged_l1_batch) + }) + } + + async fn check_consistency(&mut self) -> Result<(), Error> { + let Some(diverged_l1_batch) = self.find_last_diverged_batch().await? else { return Ok(()); - } - let diverged_l1_batch = checked_l1_batch + (root_hashes_match as u32); - self.event_handler.report_divergence(diverged_l1_batch); + }; // Check that the first L1 batch matches, to make sure that // we are actually tracking the same chain as the main node. @@ -455,15 +469,7 @@ impl ReorgDetector { ) -> Result<(), Error> { while !*stop_receiver.borrow_and_update() { let sleep_interval = match self.check_consistency().await { - Err(Error::HashMatch(HashMatchError::MissingData(MissingData::RootHash))) => { - tracing::debug!("Last L1 batch on the main node doesn't have a state root hash; waiting until it is computed"); - self.sleep_interval / 10 - } - Err(err) if err.is_retriable() => { - tracing::warn!("Following transient error occurred: {err}"); - tracing::info!("Trying again after a delay"); - self.sleep_interval - } + Err(Error::HashMatch(err)) => self.handle_hash_err(err)?, Err(err) => return Err(err), Ok(()) if stop_after_success => return Ok(()), Ok(()) => self.sleep_interval, @@ -480,6 +486,46 @@ impl ReorgDetector { } Ok(()) } + + /// Returns the sleep interval if the error is transient. + fn handle_hash_err(&self, err: HashMatchError) -> Result { + match err { + HashMatchError::MissingData(MissingData::RootHash) => { + tracing::debug!("Last L1 batch on the main node doesn't have a state root hash; waiting until it is computed"); + Ok(self.sleep_interval / 10) + } + err if err.is_retriable() => { + tracing::warn!("Following transient error occurred: {err}"); + tracing::info!("Trying again after a delay"); + Ok(self.sleep_interval) + } + err => Err(err), + } + } + + /// Checks whether a reorg is present. Unlike [`Self::run_once()`], this method doesn't pinpoint the first diverged L1 batch; + /// it just checks whether diverged batches / blocks exist in general. + /// + /// Internally retries transient errors. Returns `Ok(false)` if a stop signal is received. + pub async fn check_reorg_presence( + &mut self, + mut stop_receiver: watch::Receiver, + ) -> anyhow::Result { + while !*stop_receiver.borrow_and_update() { + let sleep_interval = match self.find_last_diverged_batch().await { + Err(err) => self.handle_hash_err(err)?, + Ok(maybe_diverged_batch) => return Ok(maybe_diverged_batch.is_some()), + }; + + if tokio::time::timeout(sleep_interval, stop_receiver.changed()) + .await + .is_ok() + { + break; + } + } + Ok(false) + } } /// Fallible and async predicate for binary search. diff --git a/core/node/reorg_detector/src/tests.rs b/core/node/reorg_detector/src/tests.rs index 5465cf8662d6..64e9c224d224 100644 --- a/core/node/reorg_detector/src/tests.rs +++ b/core/node/reorg_detector/src/tests.rs @@ -312,12 +312,19 @@ async fn reorg_is_detected_on_batch_hash_mismatch() { store_l2_block(&mut storage, 2, l2_block_hash).await; detector.check_consistency().await.unwrap(); + let (_stop_sender, stop_receiver) = watch::channel(false); + assert!(!detector + .check_reorg_presence(stop_receiver.clone()) + .await + .unwrap()); + seal_l1_batch(&mut storage, 2, H256::repeat_byte(0xff)).await; // ^ Hash of L1 batch #2 differs from that on the main node. assert_matches!( detector.check_consistency().await, Err(Error::ReorgDetected(L1BatchNumber(1))) ); + assert!(detector.check_reorg_presence(stop_receiver).await.unwrap()); } #[tokio::test] @@ -621,6 +628,9 @@ async fn reorg_is_detected_based_on_l2_block_hashes(last_correct_l1_batch: u32) detector.check_consistency().await, Err(Error::ReorgDetected(L1BatchNumber(num))) if num == last_correct_l1_batch ); + + let (_stop_sender, stop_receiver) = watch::channel(false); + assert!(detector.check_reorg_presence(stop_receiver).await.unwrap()); } #[derive(Debug)] From 4073f81e2e732c02c578cc538c43c988201da61b Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Thu, 23 Jan 2025 11:17:33 +0000 Subject: [PATCH 29/52] ci: fix typo in param and return to release token (#3522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ * [x] Fixes the typo in `component` argument * [x] Returns `RELEASE_TOKEN` to run required CI in release PR * [x] Update runner for publishing crates ## 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`. --- .github/workflows/publish-crates.yml | 4 ++-- .github/workflows/release-please.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml index 3c62fbae4f1a..ebfb96a544c8 100644 --- a/.github/workflows/publish-crates.yml +++ b/.github/workflows/publish-crates.yml @@ -3,7 +3,7 @@ name: Publish crates on: workflow_dispatch: inputs: - compontent: + component: description: 'Component to release. Possible values are: core, prover or zkstack_cli.' required: true default: 'zkstack_cli' @@ -28,7 +28,7 @@ jobs: publish-crates: name: Publish to crates.io - runs-on: ubuntu-latest + runs-on: matterlabs-ci-runner-high-performance steps: - name: Publish crates uses: matter-labs/zksync-ci-common/.github/actions/publish-crates@v1 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index c1eabbb4c45d..6a3935e9da84 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -22,7 +22,7 @@ jobs: uses: matter-labs/zksync-ci-common/.github/workflows/release-please.yaml@v1 secrets: slack_webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} # Slack webhook for notifications - gh_token: ${{ secrets.GITHUB_TOKEN }} # GitHub token for release-please + gh_token: ${{ secrets.RELEASE_TOKEN }} # GitHub token for release-please with: config: '.github/release-please/config.json' # Path to the configuration file manifest: '.github/release-please/manifest.json' # Path to the manifest file From 3e931be6bddaacbd7d029c537db03a3c191fdc21 Mon Sep 17 00:00:00 2001 From: Artem Fomiuk <88630083+Artemka374@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:19:12 +0200 Subject: [PATCH 30/52] feat: Compressor optimizations (#3476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Integrate optimizations for compressor's work. Before: PLONK: ~180s FFLONK: ~248s Now Both: ~70s ## 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`. --------- Co-authored-by: afo Co-authored-by: zksync-admin-bot2 <91326834+zksync-admin-bot2@users.noreply.github.com> --- .dockerignore | 1 + .../build-proof-fri-gpu-compressor-gar.yml | 2 +- .gitignore | 2 + core/Cargo.lock | 97 +- core/Cargo.toml | 15 +- core/lib/prover_interface/Cargo.toml | 1 + core/lib/prover_interface/src/outputs.rs | 25 +- .../proof-fri-gpu-compressor-gar/Dockerfile | 7 +- etc/env/base/contracts.toml | 4 +- etc/env/base/fri_proof_compressor.toml | 4 +- etc/env/file_based/general.yaml | 4 +- prover/Cargo.lock | 149 +- prover/Cargo.toml | 21 +- .../proof_fri_compressor/src/compressor.rs | 212 +- .../bin/proof_fri_compressor/src/main.rs | 19 +- .../Cargo.toml | 2 +- .../src/main.rs | 159 +- .../src/utils.rs | 141 - prover/crates/lib/keystore/Cargo.toml | 3 +- prover/crates/lib/keystore/src/compressor.rs | 221 ++ prover/crates/lib/keystore/src/keystore.rs | 98 +- prover/crates/lib/keystore/src/lib.rs | 3 + .../lib/keystore/src/setup_data_generator.rs | 110 +- prover/crates/lib/prover_fri_types/src/lib.rs | 12 - .../keys/finalization_hints_compression_1.bin | Bin 120 -> 373 bytes .../keys/finalization_hints_compression_2.bin | Bin 120 -> 367 bytes .../keys/finalization_hints_compression_3.bin | Bin 120 -> 366 bytes .../keys/finalization_hints_compression_4.bin | Bin 2024 -> 18934 bytes ...nalization_hints_compression_wrapper_1.bin | 35 + ...nalization_hints_compression_wrapper_5.bin | Bin 2024 -> 19057 bytes .../keys/verification_compression_1_key.json | 476 ++- .../keys/verification_compression_2_key.json | 498 ++- .../keys/verification_compression_3_key.json | 498 ++- .../keys/verification_compression_4_key.json | 3422 ++++++++--------- ...erification_compression_wrapper_1_key.json | 260 ++ ...erification_compression_wrapper_5_key.json | 424 +- .../data/keys/verification_scheduler_key.json | 490 ++- prover/setup-data-gpu-keys.json | 6 +- .../crates/zkstack/completion/_zkstack.zsh | 8 +- .../crates/zkstack/completion/zkstack.fish | 12 +- .../crates/zkstack/completion/zkstack.sh | 24 +- .../commands/prover/args/compressor_keys.rs | 53 +- .../zkstack/src/commands/prover/args/init.rs | 18 +- .../src/commands/prover/compressor_keys.rs | 81 +- .../zkstack/src/commands/prover/init.rs | 47 +- 45 files changed, 3791 insertions(+), 3873 deletions(-) delete mode 100644 prover/crates/bin/vk_setup_data_generator_server_fri/src/utils.rs create mode 100644 prover/crates/lib/keystore/src/compressor.rs create mode 100644 prover/data/keys/finalization_hints_compression_wrapper_1.bin create mode 100644 prover/data/keys/verification_compression_wrapper_1_key.json diff --git a/.dockerignore b/.dockerignore index 9be9fd580858..528c0ed9793a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -25,6 +25,7 @@ keys/setup !contracts/ !setup_2\^26.key !setup_2\^24.key +!setup_compact.key # It's required to remove .git from contracts, # otherwise yarn tries to use .git parent directory that # doesn't exist. diff --git a/.github/workflows/build-proof-fri-gpu-compressor-gar.yml b/.github/workflows/build-proof-fri-gpu-compressor-gar.yml index 05255eeb2c8d..aeaaf5ff190d 100644 --- a/.github/workflows/build-proof-fri-gpu-compressor-gar.yml +++ b/.github/workflows/build-proof-fri-gpu-compressor-gar.yml @@ -28,7 +28,7 @@ jobs: - name: Download FFLONK key and setup data run: | gsutil -m rsync -r gs://matterlabs-setup-data-us/${{ inputs.setup_keys_id }} docker/proof-fri-gpu-compressor-gar - gsutil -m cp -r gs://matterlabs-setup-keys-us/setup-keys/setup_fflonk_compact.key docker/proof-fri-gpu-compressor-gar + gsutil -m cp -r gs://matterlabs-setup-keys-us/setup-keys/setup_compact.key docker/proof-fri-gpu-compressor-gar - name: Login to us-central1 GAR run: | diff --git a/.gitignore b/.gitignore index d92880f5ae48..716024531d4a 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,8 @@ hyperchain-*.yml # Prover keys that should not be commited prover/crates/bin/vk_setup_data_generator_server_fri/data/setup_* prover/data/keys/setup_* +prover/data/keys/fflonk_setup_snark_data.bin +prover/data/keys/plonk_setup_snark_data.bin # ZK Stack CLI chains/era/configs/* diff --git a/core/Cargo.lock b/core/Cargo.lock index 3f9e1ee93561..1b251bec741d 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1289,9 +1289,9 @@ dependencies = [ [[package]] name = "boojum" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14bd053feb7173130679a2119e105b5e78af7eb6b0e752de6793e4ee63d8e899" +checksum = "d689807d79092f8f7cfcb72a2313a43da77d56314e41324810566f385875c185" dependencies = [ "arrayvec 0.7.6", "bincode", @@ -1690,9 +1690,9 @@ dependencies = [ [[package]] name = "circuit_definitions" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ebc81d5c2f6ee8de436c242f6466fb315fe25afcbc81aa1c47dfca39a55403" +checksum = "1f04f9c7c6b39255199aaba49802c5f40f95bcff24f5a456446a912d254f4bb1" dependencies = [ "circuit_encodings", "crossbeam", @@ -1704,26 +1704,26 @@ dependencies = [ [[package]] name = "circuit_encodings" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33375d2448a78c1aed9b8755f7939a6b6f19e2fa80f44f4930a5b4c2bb7cbb44" +checksum = "fc3399f1981164c3c687ea15b1eedd35a16f28069c845a24530de21f996f3fdd" dependencies = [ "derivative", "serde", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zkevm_circuits", ] [[package]] name = "circuit_sequencer_api" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2fec5c28e5a9f085279e70e13b2eebb63a95ee0bfb99d58095ac01c1c7b256" +checksum = "b5583037ec61607ac481b0c887b7fb4f860e65c92c6f3f7be74f6bab7c40c3ce" dependencies = [ "derivative", "rayon", "serde", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zksync_bellman", ] @@ -2983,9 +2983,9 @@ dependencies = [ [[package]] name = "fflonk" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63d70f1cbf9e572ccaf22ca1dfce4b93ff48b9a5e8dd70de50d87edb960d173" +checksum = "b36c5fa909ab71b7eb4b8f7fd092f72ed83b93f2615e42f245ca808d8f308917" dependencies = [ "bincode", "byteorder", @@ -3272,9 +3272,9 @@ dependencies = [ [[package]] name = "franklin-crypto" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d7b8e5864df7f3747e5e64a5b87b4a57aa2a4a20c55c9e96a3a305a8143c45" +checksum = "8309d8fc22fc389d831390473b0ee9fe94e85f19a8b9229b9aec8aa73f5bcee3" dependencies = [ "arr_macro", "bit-vec", @@ -7258,9 +7258,9 @@ dependencies = [ [[package]] name = "rescue_poseidon" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c250446885c257bee70bc0f2600229ce72f03073b87fb8f5dd278dba16b11f30" +checksum = "5e631fd184b6d2f2c04f9dc75405289d99fd0d6612d8dfbb478c01bfbab648fb" dependencies = [ "addchain", "arrayvec 0.7.6", @@ -8665,9 +8665,9 @@ dependencies = [ [[package]] name = "snark_wrapper" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f361c2c47b71ee43f62954ce69f7730e14acb7fb3b0f2c697da02f97327c569" +checksum = "eddb498315057210abd25e2fbe2ea30ab69a07ca0c166406a3e7c056ec8fbbfd" dependencies = [ "derivative", "rand 0.4.6", @@ -11160,9 +11160,9 @@ dependencies = [ [[package]] name = "zk_evm" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ee848aa90ae045457795b1c0afeb388fbd9fa1e57aa0e8791b28f405e7cc2c" +checksum = "f11d0310228af78e804e5e7deccd1ad6797fce1c44c3b8016722ab78dc183c4a" dependencies = [ "anyhow", "lazy_static", @@ -11170,7 +11170,7 @@ dependencies = [ "serde", "serde_json", "static_assertions", - "zk_evm_abstractions 0.150.19", + "zk_evm_abstractions 0.150.20", ] [[package]] @@ -11201,22 +11201,22 @@ dependencies = [ [[package]] name = "zk_evm_abstractions" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f08feaa3e3d99e1e57234fe6ba2aa062609492c6499b2344121c4a699292ab7" +checksum = "d7616edbdeeeb214211e9bdc4346b6a62c6c6118c3d2b83b7db24c01f65f6e25" dependencies = [ "anyhow", "num_enum 0.6.1", "serde", "static_assertions", - "zkevm_opcode_defs 0.150.19", + "zkevm_opcode_defs 0.150.20", ] [[package]] name = "zkevm_circuits" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760cfbbce18f42bbecd2565de9bf658234cac2431cce9b0c1df08e9df645d467" +checksum = "6f36004572f5086c513715e11f38230e2538c159d4f5d90dc518833c6fc78293" dependencies = [ "arrayvec 0.7.6", "boojum", @@ -11228,7 +11228,7 @@ dependencies = [ "seq-macro", "serde", "smallvec", - "zkevm_opcode_defs 0.150.19", + "zkevm_opcode_defs 0.150.20", "zksync_cs_derive", ] @@ -11276,9 +11276,9 @@ dependencies = [ [[package]] name = "zkevm_opcode_defs" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2bd8ef52c8f9911dd034b91d29f087ab52f80a80f9d996deb881abbb953793" +checksum = "ce6b4a47c0e7f95b51d29ca336821321cec4bbba0acdd412c3a209270a0d37fe" dependencies = [ "bitflags 2.6.0", "blake2 0.10.6", @@ -11337,9 +11337,9 @@ dependencies = [ [[package]] name = "zksync_bellman" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06d424f7e3862d7a6715179bafffbe7a5dce17129f95ac4124502ab9f1edfb8" +checksum = "78fc3c598daf718b6fc791bfbb01c4634199e479ea9b2c82d06cd108b967d441" dependencies = [ "arrayvec 0.7.6", "bit-vec", @@ -11411,7 +11411,7 @@ dependencies = [ "tokio", "tracing", "vise", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zksync_contracts", "zksync_dal", "zksync_eth_client", @@ -11756,9 +11756,9 @@ dependencies = [ [[package]] name = "zksync_cs_derive" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23237b019a469bfa59c11108beff84a63a43f52fa3afbf1b461527031fc47644" +checksum = "97ab7469afcd9e1cb220fe17b3c9f2abe031648b94add97da37065c58be08554" dependencies = [ "proc-macro-error", "proc-macro2 1.0.92", @@ -12087,9 +12087,9 @@ dependencies = [ [[package]] name = "zksync_ff" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5aa518ed0ea7ef737d50de02025f5a593dbb11104b3c1bf5a00f39581b47dc" +checksum = "6583c2db6dc787600879d27ec98d2eb628a757ee41831e54f8be1dae4acc599f" dependencies = [ "byteorder", "hex", @@ -12100,9 +12100,9 @@ dependencies = [ [[package]] name = "zksync_ff_derive" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b43100a1278e2f64820368db8751c2441860ea74ab5749074cf8f864647af" +checksum = "8f62e93dde881d8dd44d1864c7682394dde6d18e582fc5af78768221a1766fdf" dependencies = [ "num-bigint 0.4.6", "num-integer", @@ -12145,9 +12145,9 @@ dependencies = [ [[package]] name = "zksync_kzg" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9da880b8282a97d9dfd6ac9f0189d310c0602059a8de20aa66a883979d6adba" +checksum = "174f82592590901cbcf2b298059c89f817b404299ffbd050a3915ea72357f545" dependencies = [ "boojum", "derivative", @@ -12295,7 +12295,7 @@ dependencies = [ "zk_evm 0.133.0", "zk_evm 0.140.0", "zk_evm 0.141.0", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zksync_contracts", "zksync_eth_signer", "zksync_mini_merkle_tree", @@ -12335,7 +12335,7 @@ dependencies = [ "tower-http 0.5.2", "tracing", "vise", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zksync_config", "zksync_consensus_roles", "zksync_contracts", @@ -12634,9 +12634,9 @@ dependencies = [ [[package]] name = "zksync_pairing" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f0d96f3e386f3b4c76a614d73b71714d6712e917d462bf8053b8af352da0b3" +checksum = "baafdd03ca7a48dc9b6808be3630f2d8a003aa425d71946e9158d8c0aeb1cc79" dependencies = [ "byteorder", "cfg-if", @@ -12741,6 +12741,7 @@ dependencies = [ "serde_with", "strum", "tokio", + "zksync_bellman", "zksync_object_store", "zksync_types", "zksync_vm_interface", @@ -12845,9 +12846,9 @@ dependencies = [ [[package]] name = "zksync_solidity_vk_codegen" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb10f377dcc24fe2268cc5f530c16af1c879a791570d8fe64064b58ba143c7cc" +checksum = "bb05a12f5552d7947427f755e29f548ce94733851f1fa16edaf8b75c28033e73" dependencies = [ "ethereum-types", "franklin-crypto", @@ -13088,8 +13089,8 @@ source = "git+https://github.com/matter-labs/vm2.git?rev=457d8a7eea9093af9440662 dependencies = [ "enum_dispatch", "primitive-types", - "zk_evm_abstractions 0.150.19", - "zkevm_opcode_defs 0.150.19", + "zk_evm_abstractions 0.150.20", + "zkevm_opcode_defs 0.150.20", "zksync_vm2_interface", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index 3b6ba37c28a6..80e9ac035283 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -228,18 +228,19 @@ tokio-stream = "0.1.16" # We *always* pin the latest version of protocol to disallow accidental changes in the execution logic. # However, for the historical version of protocol crates, we have lax requirements. Otherwise, # Bumping a crypto dependency like `boojum` would require us to republish all the historical packages. -circuit_encodings = "=0.150.19" -circuit_sequencer_api = "=0.150.19" -circuit_definitions = "=0.150.19" -crypto_codegen = { package = "zksync_solidity_vk_codegen",version = "=0.30.12" } -kzg = { package = "zksync_kzg", version = "=0.150.19" } +circuit_encodings = "=0.150.20" +circuit_sequencer_api = "=0.150.20" +circuit_definitions = "=0.150.20" +crypto_codegen = { package = "zksync_solidity_vk_codegen",version = "=0.30.13" } +kzg = { package = "zksync_kzg", version = "=0.150.20" } zk_evm = { version = "=0.133.0" } zk_evm_1_3_1 = { package = "zk_evm", version = "0.131.0-rc.2" } zk_evm_1_3_3 = { package = "zk_evm", version = "0.133" } zk_evm_1_4_0 = { package = "zk_evm", version = "0.140" } zk_evm_1_4_1 = { package = "zk_evm", version = "0.141" } -zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.19" } -fflonk = "=0.30.12" +zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.20" } +fflonk = "=0.30.13" +bellman = {package = "zksync_bellman", version = "=0.30.13"} # New VM; pinned to a specific commit because of instability zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "457d8a7eea9093af9440662e33e598c13ba41633" } diff --git a/core/lib/prover_interface/Cargo.toml b/core/lib/prover_interface/Cargo.toml index 533e3bc1296a..dfa860c5aa6b 100644 --- a/core/lib/prover_interface/Cargo.toml +++ b/core/lib/prover_interface/Cargo.toml @@ -18,6 +18,7 @@ zksync_types.workspace = true # We can use the newest api to send proofs to L1. circuit_definitions.workspace = true fflonk.workspace = true +bellman.workspace = true circuit_sequencer_api.workspace = true serde.workspace = true diff --git a/core/lib/prover_interface/src/outputs.rs b/core/lib/prover_interface/src/outputs.rs index 1c3a613628d3..3fc037d0c040 100644 --- a/core/lib/prover_interface/src/outputs.rs +++ b/core/lib/prover_interface/src/outputs.rs @@ -1,10 +1,12 @@ use core::fmt; +use bellman::plonk::better_better_cs::proof::Proof as PlonkProof; use circuit_definitions::{ boojum::pairing::bn256::Bn256, - circuit_definitions::aux_layer::ZkSyncSnarkWrapperCircuitNoLookupCustomGate, + circuit_definitions::aux_layer::{ + ZkSyncSnarkWrapperCircuit, ZkSyncSnarkWrapperCircuitNoLookupCustomGate, + }, }; -use circuit_sequencer_api::proof::FinalProof; use fflonk::FflonkProof; use serde::{Deserialize, Serialize}; use serde_with::{hex::Hex, serde_as}; @@ -43,10 +45,18 @@ pub struct FflonkL1BatchProofForL1 { pub protocol_version: ProtocolSemanticVersion, } +// Implementation created to allow conversion from FflonkL1BatchProofForL1(which is old L1BatchProofForL1) +// to L1BatchProofForL1 to avoid compatibility problems with serialization/deserialization +impl From for L1BatchProofForL1 { + fn from(proof: FflonkL1BatchProofForL1) -> Self { + L1BatchProofForL1::Fflonk(proof) + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct PlonkL1BatchProofForL1 { pub aggregation_result_coords: [[u8; 32]; 4], - pub scheduler_proof: FinalProof, + pub scheduler_proof: PlonkProof, pub protocol_version: ProtocolSemanticVersion, } @@ -125,11 +135,12 @@ impl StoredObject for L1BatchProofForL1 { } fn deserialize(bytes: Vec) -> Result { - zksync_object_store::bincode::deserialize::(&bytes).or_else(|_| { - zksync_object_store::bincode::deserialize::(&bytes) + match zksync_object_store::bincode::deserialize::(&bytes) { + Ok(proof) => Ok(proof.into()), + Err(_) => zksync_object_store::bincode::deserialize::(&bytes) .map(Into::into) - .map_err(Into::into) - }) + .map_err(Into::into), + } } } diff --git a/docker/proof-fri-gpu-compressor-gar/Dockerfile b/docker/proof-fri-gpu-compressor-gar/Dockerfile index 42127ea6126c..d74440bd009b 100644 --- a/docker/proof-fri-gpu-compressor-gar/Dockerfile +++ b/docker/proof-fri-gpu-compressor-gar/Dockerfile @@ -4,18 +4,15 @@ FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04 as app # HACK copying to root is the only way to make Docker layer caching work for these files for some reason COPY *.bin / -COPY ./setup_fflonk_compact.key /setup_fflonk_compact.key +COPY ./setup_compact.key /setup_compact.key RUN apt-get update && apt-get install -y curl libpq5 ca-certificates && rm -rf /var/lib/apt/lists/* - # copy finalization hints required for assembly generation -COPY --from=proof_fri_gpu /setup_2\^24.key /setup_2\^24.key COPY --from=proof_fri_gpu /prover/data/keys/ /prover/data/keys/ COPY --from=proof_fri_gpu /usr/bin/zksync_proof_fri_compressor /usr/bin/ -ENV CRS_FILE=/setup_2\^24.key -ENV COMPACT_CRS_FILE=/setup_fflonk_compact.key +ENV COMPACT_CRS_FILE=/setup_compact.key ENTRYPOINT ["zksync_proof_fri_compressor"] diff --git a/etc/env/base/contracts.toml b/etc/env/base/contracts.toml index 1cb22440e33c..d3eaabf92bd8 100644 --- a/etc/env/base/contracts.toml +++ b/etc/env/base/contracts.toml @@ -39,7 +39,7 @@ BLOB_VERSIONED_HASH_RETRIEVER_ADDR = "0x0000000000000000000000000000000000000000 GENESIS_ROOT = "0x09e68951458b18c24ae5f4100160b53c4888c9b3c3c1859cc674bc02236675ad" GENESIS_BATCH_COMMITMENT = "0x7238eab6a0e9f5bb84421feae6b6b9ae80816d490c875d29ff3ded375a3e078f" -GENESIS_ROLLUP_LEAF_INDEX = "64" +GENESIS_ROLLUP_LEAF_INDEX = "64" # Ecosystem-wide params L1_ROLLUP_DA_VALIDATOR = "0x0000000000000000000000000000000000000000" @@ -67,7 +67,7 @@ L1_NATIVE_TOKEN_VAULT_IMPL_ADDR ="0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF" L1_NATIVE_TOKEN_VAULT_PROXY_ADDR ="0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF" L2_NATIVE_TOKEN_VAULT_IMPL_ADDR = "0x0000000000000000000000000000000000010004" L2_NATIVE_TOKEN_VAULT_PROXY_ADDR = "0x0000000000000000000000000000000000010004" -L2_SHARED_BRIDGE_IMPL_ADDR = "0x0000000000000000000000000000000000010003" +L2_SHARED_BRIDGE_IMPL_ADDR = "0x0000000000000000000000000000000000010003" L2_SHARED_BRIDGE_ADDR = "0x0000000000000000000000000000000000010003" L2_ERC20_BRIDGE_ADDR = "0x0000000000000000000000000000000000010003" CTM_DEPLOYMENT_TRACKER_IMPL_ADDR ="0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF" diff --git a/etc/env/base/fri_proof_compressor.toml b/etc/env/base/fri_proof_compressor.toml index a8825ca98613..c8855d6b2a48 100644 --- a/etc/env/base/fri_proof_compressor.toml +++ b/etc/env/base/fri_proof_compressor.toml @@ -5,8 +5,8 @@ prometheus_pushgateway_url = "http://127.0.0.1:9091" prometheus_push_interval_ms = 100 generation_timeout_in_secs = 3600 max_attempts = 5 -universal_setup_path = "../keys/setup/setup_2^24.key" -universal_setup_download_url = "https://storage.googleapis.com/matterlabs-setup-keys-us/setup-keys/setup_2^24.key" +universal_setup_path = "../keys/setup/setup_compact.key" +universal_setup_download_url = "https://storage.googleapis.com/matterlabs-setup-keys-us/setup-keys/setup_compact.key" verify_wrapper_proof = true universal_fflonk_setup_path = "../keys/setup/setup_fflonk_compact.key" universal_fflonk_setup_download_url = "https://storage.googleapis.com/matterlabs-setup-keys-us/setup-keys/setup_fflonk_compact.key" diff --git a/etc/env/file_based/general.yaml b/etc/env/file_based/general.yaml index f15b63a757f7..ece5a1156c5f 100644 --- a/etc/env/file_based/general.yaml +++ b/etc/env/file_based/general.yaml @@ -182,8 +182,8 @@ proof_compressor: prometheus_push_interval_ms: 100 generation_timeout_in_secs: 3600 max_attempts: 5 - universal_setup_path: keys/setup/setup_2^24.key - universal_setup_download_url: https://storage.googleapis.com/matterlabs-setup-keys-us/setup-keys/setup_2^24.key + universal_setup_path: keys/setup/setup_compact.key + universal_setup_download_url: https://storage.googleapis.com/matterlabs-setup-keys-us/setup-keys/setup_compact.key verify_wrapper_proof: true universal_fflonk_setup_path: keys/setup/setup_fflonk_compact.key universal_fflonk_setup_download_url: https://storage.googleapis.com/matterlabs-setup-keys-us/setup-keys/setup_fflonk_compact.key diff --git a/prover/Cargo.lock b/prover/Cargo.lock index a9a8d7e71c54..19223f360857 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -625,9 +625,9 @@ dependencies = [ [[package]] name = "boojum" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14bd053feb7173130679a2119e105b5e78af7eb6b0e752de6793e4ee63d8e899" +checksum = "d689807d79092f8f7cfcb72a2313a43da77d56314e41324810566f385875c185" dependencies = [ "arrayvec 0.7.6", "bincode", @@ -657,9 +657,9 @@ dependencies = [ [[package]] name = "boojum-cuda" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd43bc7fc457920cb3b823e4f95ffbbf180b2c48b8d643125cd121325cdd8db" +checksum = "896aeb550e4b92e6c96858c1d0aa8413c00f97fb91f321a2bf3ed912942870f8" dependencies = [ "boojum", "cmake", @@ -805,9 +805,9 @@ dependencies = [ [[package]] name = "circuit_definitions" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ebc81d5c2f6ee8de436c242f6466fb315fe25afcbc81aa1c47dfca39a55403" +checksum = "1f04f9c7c6b39255199aaba49802c5f40f95bcff24f5a456446a912d254f4bb1" dependencies = [ "circuit_encodings", "crossbeam", @@ -819,26 +819,26 @@ dependencies = [ [[package]] name = "circuit_encodings" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33375d2448a78c1aed9b8755f7939a6b6f19e2fa80f44f4930a5b4c2bb7cbb44" +checksum = "fc3399f1981164c3c687ea15b1eedd35a16f28069c845a24530de21f996f3fdd" dependencies = [ "derivative", "serde", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zkevm_circuits", ] [[package]] name = "circuit_sequencer_api" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2fec5c28e5a9f085279e70e13b2eebb63a95ee0bfb99d58095ac01c1c7b256" +checksum = "b5583037ec61607ac481b0c887b7fb4f860e65c92c6f3f7be74f6bab7c40c3ce" dependencies = [ "derivative", "rayon", "serde", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zksync_bellman", ] @@ -1706,9 +1706,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "era_cudart" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f881cf689ba889bb0fa04c0e71aba701acd7fafd3fa545e3f2782f2a8c0ba0" +checksum = "6ff6fc4fba6bf756cdebd6750161d280af2859d217dad89bfb2823ac760bf0e8" dependencies = [ "bitflags 2.6.0", "era_cudart_sys", @@ -1717,11 +1717,11 @@ dependencies = [ [[package]] name = "era_cudart_sys" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f29cbd9e6d97fc1f05b484f960e921fe69548b4773a361b2e403e4cb9d6d575" +checksum = "7e0daeb39d2111a868a50e0bd7d90fa355f93022038088c0dd865bbdda1113ef" dependencies = [ - "serde_json", + "regex-lite", ] [[package]] @@ -1838,9 +1838,9 @@ dependencies = [ [[package]] name = "fflonk" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63d70f1cbf9e572ccaf22ca1dfce4b93ff48b9a5e8dd70de50d87edb960d173" +checksum = "b36c5fa909ab71b7eb4b8f7fd092f72ed83b93f2615e42f245ca808d8f308917" dependencies = [ "bincode", "byteorder", @@ -1855,9 +1855,9 @@ dependencies = [ [[package]] name = "fflonk-cuda" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b008e6158c95747b3b135adbd7f6d563c406849a10c00abfef109b4d0442a589" +checksum = "890b635123fe176814ddbda1fbe006c55ca02375e5dde83539018f283219a8ba" dependencies = [ "bincode", "byteorder", @@ -1975,9 +1975,9 @@ dependencies = [ [[package]] name = "franklin-crypto" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d7b8e5864df7f3747e5e64a5b87b4a57aa2a4a20c55c9e96a3a305a8143c45" +checksum = "8309d8fc22fc389d831390473b0ee9fe94e85f19a8b9229b9aec8aa73f5bcee3" dependencies = [ "arr_macro", "bit-vec 0.6.3", @@ -4550,9 +4550,9 @@ dependencies = [ [[package]] name = "proof-compression" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40624617ed1535349cf31671a7091703d3a31e64d6a7760a5e952c68ee1f4f0e" +checksum = "de4c8014afcd29bfe93ac3bd1ea0d9f2da06fa9895337bead3f3d0d904080e36" dependencies = [ "bincode", "byteorder", @@ -4562,6 +4562,7 @@ dependencies = [ "serde", "serde_json", "shivini", + "zksync-gpu-prover", ] [[package]] @@ -4945,6 +4946,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -5070,9 +5077,9 @@ dependencies = [ [[package]] name = "rescue_poseidon" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c250446885c257bee70bc0f2600229ce72f03073b87fb8f5dd278dba16b11f30" +checksum = "5e631fd184b6d2f2c04f9dc75405289d99fd0d6612d8dfbb478c01bfbab648fb" dependencies = [ "addchain", "arrayvec 0.7.6", @@ -5860,9 +5867,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shivini" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c336213e4ec7651d2984e892326d09c195ee166493c192b0d8aad36288e5c5f" +checksum = "8937f1fe25a1ea33a40bdf560847b934fe68322c40cd54dd77ab433128022cce" dependencies = [ "bincode", "boojum", @@ -5951,9 +5958,9 @@ dependencies = [ [[package]] name = "snark_wrapper" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f361c2c47b71ee43f62954ce69f7730e14acb7fb3b0f2c697da02f97327c569" +checksum = "eddb498315057210abd25e2fbe2ea30ab69a07ca0c166406a3e7c056ec8fbbfd" dependencies = [ "derivative", "rand 0.4.6", @@ -7839,9 +7846,9 @@ dependencies = [ [[package]] name = "zk_evm" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ee848aa90ae045457795b1c0afeb388fbd9fa1e57aa0e8791b28f405e7cc2c" +checksum = "f11d0310228af78e804e5e7deccd1ad6797fce1c44c3b8016722ab78dc183c4a" dependencies = [ "anyhow", "lazy_static", @@ -7849,7 +7856,7 @@ dependencies = [ "serde", "serde_json", "static_assertions", - "zk_evm_abstractions 0.150.19", + "zk_evm_abstractions 0.150.20", ] [[package]] @@ -7880,22 +7887,22 @@ dependencies = [ [[package]] name = "zk_evm_abstractions" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f08feaa3e3d99e1e57234fe6ba2aa062609492c6499b2344121c4a699292ab7" +checksum = "d7616edbdeeeb214211e9bdc4346b6a62c6c6118c3d2b83b7db24c01f65f6e25" dependencies = [ "anyhow", "num_enum 0.6.1", "serde", "static_assertions", - "zkevm_opcode_defs 0.150.19", + "zkevm_opcode_defs 0.150.20", ] [[package]] name = "zkevm-assembly" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd4bc83f3a711d820829dccce24fa59ab4c588c2745203ec6a6ad8c871362b7" +checksum = "c2dc9539ce7f550231934e6b1faae23387fd132f1ac053b8e674d30968158bff" dependencies = [ "env_logger 0.9.3", "hex", @@ -7908,14 +7915,14 @@ dependencies = [ "smallvec", "structopt", "thiserror 1.0.69", - "zkevm_opcode_defs 0.150.19", + "zkevm_opcode_defs 0.150.20", ] [[package]] name = "zkevm_circuits" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760cfbbce18f42bbecd2565de9bf658234cac2431cce9b0c1df08e9df645d467" +checksum = "6f36004572f5086c513715e11f38230e2538c159d4f5d90dc518833c6fc78293" dependencies = [ "arrayvec 0.7.6", "boojum", @@ -7927,7 +7934,7 @@ dependencies = [ "seq-macro", "serde", "smallvec", - "zkevm_opcode_defs 0.150.19", + "zkevm_opcode_defs 0.150.20", "zksync_cs_derive", ] @@ -7975,9 +7982,9 @@ dependencies = [ [[package]] name = "zkevm_opcode_defs" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2bd8ef52c8f9911dd034b91d29f087ab52f80a80f9d996deb881abbb953793" +checksum = "ce6b4a47c0e7f95b51d29ca336821321cec4bbba0acdd412c3a209270a0d37fe" dependencies = [ "bitflags 2.6.0", "blake2 0.10.6", @@ -7992,9 +7999,9 @@ dependencies = [ [[package]] name = "zkevm_test_harness" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24b28c5b855e4e28d85455b48f346e9d46a00c6af84d5bbc38e5b5f7410b5cb" +checksum = "36ed8dd80455d90a51a6618a5bc07685beaad582cabca71ccef25866cd73993b" dependencies = [ "bincode", "circuit_definitions", @@ -8020,9 +8027,9 @@ dependencies = [ [[package]] name = "zksync-gpu-ffi" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f6a84e4e361977a2dc5dbe783e3856e40b1050dc1b9bb3e9833a5e59c20697" +checksum = "6c3fbd4c8df140131d28b05581b19418bc5e561beb21dec6f24ca2f34343399c" dependencies = [ "cmake", "crossbeam", @@ -8035,9 +8042,9 @@ dependencies = [ [[package]] name = "zksync-gpu-prover" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba58bbaf4920c635553d3dfb7796636223f55e75ae6512eb9c98f48f0a03215" +checksum = "0256175ceb3ea675d4c0ebcd690fdd45138bab1c5bc298b2e0db320e5abc0bdb" dependencies = [ "bit-vec 0.6.3", "cfg-if", @@ -8052,9 +8059,9 @@ dependencies = [ [[package]] name = "zksync-wrapper-prover" -version = "0.152.10" +version = "0.152.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4bcf41946f95a1e64ce99cde1d54966a04c5ef2c89d9a87f0fa61e39987510b" +checksum = "390d8f99cf47fade7f2fe38925f9787b3d27641a878887ae980e4ab5f6731ac0" dependencies = [ "circuit_definitions", "zkevm_test_harness", @@ -8084,9 +8091,9 @@ dependencies = [ [[package]] name = "zksync_bellman" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06d424f7e3862d7a6715179bafffbe7a5dce17129f95ac4124502ab9f1edfb8" +checksum = "78fc3c598daf718b6fc791bfbb01c4634199e479ea9b2c82d06cd108b967d441" dependencies = [ "arrayvec 0.7.6", "bit-vec 0.6.3", @@ -8310,9 +8317,9 @@ dependencies = [ [[package]] name = "zksync_cs_derive" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23237b019a469bfa59c11108beff84a63a43f52fa3afbf1b461527031fc47644" +checksum = "97ab7469afcd9e1cb220fe17b3c9f2abe031648b94add97da37065c58be08554" dependencies = [ "proc-macro-error", "proc-macro2 1.0.92", @@ -8412,9 +8419,9 @@ dependencies = [ [[package]] name = "zksync_ff" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5aa518ed0ea7ef737d50de02025f5a593dbb11104b3c1bf5a00f39581b47dc" +checksum = "6583c2db6dc787600879d27ec98d2eb628a757ee41831e54f8be1dae4acc599f" dependencies = [ "byteorder", "hex", @@ -8425,9 +8432,9 @@ dependencies = [ [[package]] name = "zksync_ff_derive" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b43100a1278e2f64820368db8751c2441860ea74ab5749074cf8f864647af" +checksum = "8f62e93dde881d8dd44d1864c7682394dde6d18e582fc5af78768221a1766fdf" dependencies = [ "num-bigint 0.4.6", "num-integer", @@ -8440,9 +8447,9 @@ dependencies = [ [[package]] name = "zksync_kzg" -version = "0.150.19" +version = "0.150.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9da880b8282a97d9dfd6ac9f0189d310c0602059a8de20aa66a883979d6adba" +checksum = "174f82592590901cbcf2b298059c89f817b404299ffbd050a3915ea72357f545" dependencies = [ "boojum", "derivative", @@ -8498,7 +8505,7 @@ dependencies = [ "zk_evm 0.133.0", "zk_evm 0.140.0", "zk_evm 0.141.0", - "zk_evm 0.150.19", + "zk_evm 0.150.20", "zksync_contracts", "zksync_mini_merkle_tree", "zksync_system_constants", @@ -8532,9 +8539,9 @@ dependencies = [ [[package]] name = "zksync_pairing" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f0d96f3e386f3b4c76a614d73b71714d6712e917d462bf8053b8af352da0b3" +checksum = "baafdd03ca7a48dc9b6808be3630f2d8a003aa425d71946e9158d8c0aeb1cc79" dependencies = [ "byteorder", "cfg-if", @@ -8784,6 +8791,7 @@ dependencies = [ "serde", "serde_with", "strum", + "zksync_bellman", "zksync_object_store", "zksync_types", "zksync_vm_interface", @@ -8841,6 +8849,7 @@ dependencies = [ "hex", "md5", "once_cell", + "proof-compression", "serde", "serde_json", "sha3 0.10.8", @@ -8867,9 +8876,9 @@ dependencies = [ [[package]] name = "zksync_solidity_vk_codegen" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb10f377dcc24fe2268cc5f530c16af1c879a791570d8fe64064b58ba143c7cc" +checksum = "bb05a12f5552d7947427f755e29f548ce94733851f1fa16edaf8b75c28033e73" dependencies = [ "ethereum-types", "franklin-crypto", @@ -8993,8 +9002,8 @@ source = "git+https://github.com/matter-labs/vm2.git?rev=457d8a7eea9093af9440662 dependencies = [ "enum_dispatch", "primitive-types", - "zk_evm_abstractions 0.150.19", - "zkevm_opcode_defs 0.150.19", + "zk_evm_abstractions 0.150.20", + "zkevm_opcode_defs 0.150.20", "zksync_vm2_interface", ] diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 6c249b7094ae..194f6b90a2e7 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -67,19 +67,18 @@ tracing-test = "0.2.5" url = "2.5.2" vise = "0.2.0" -# Proving dependencies -circuit_definitions = "=0.150.19" -circuit_sequencer_api = "=0.150.19" -zkevm_test_harness = "=0.150.19" -proof-compression-gpu = { package = "proof-compression", version = "=0.152.10" } -fflonk-gpu = { package = "fflonk-cuda", version = "=0.152.10" } -fflonk = "=0.30.12" -franklin-crypto = "=0.30.12" +circuit_definitions = "=0.150.20" +circuit_sequencer_api = "=0.150.20" +zkevm_test_harness = "=0.150.20" +fflonk = "=0.30.13" +franklin-crypto = "=0.30.13" # GPU proving dependencies -wrapper_prover = { package = "zksync-wrapper-prover", version = "=0.152.10" } -shivini = "=0.152.10" -boojum-cuda = "=0.152.10" +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" # Core workspace dependencies zksync_multivm = { version = "=26.1.0-non-semver-compat", path = "../core/lib/multivm" } diff --git a/prover/crates/bin/proof_fri_compressor/src/compressor.rs b/prover/crates/bin/proof_fri_compressor/src/compressor.rs index 3671fa183b5d..581e1fed8a48 100644 --- a/prover/crates/bin/proof_fri_compressor/src/compressor.rs +++ b/prover/crates/bin/proof_fri_compressor/src/compressor.rs @@ -2,27 +2,14 @@ use std::{sync::Arc, time::Instant}; use anyhow::Context as _; use async_trait::async_trait; -use circuit_sequencer_api::proof::FinalProof; -use fflonk_gpu::{FflonkSnarkVerifierCircuit, FflonkSnarkVerifierCircuitProof}; +use proof_compression_gpu::{run_proof_chain, SnarkWrapper, SnarkWrapperProof}; use tokio::task::JoinHandle; -use wrapper_prover::{GPUWrapperConfigs, WrapperProver}; -use zkevm_test_harness::proof_wrapper_utils::{get_trusted_setup, DEFAULT_WRAPPER_CONFIG}; use zksync_object_store::ObjectStore; use zksync_prover_dal::{ConnectionPool, Prover, ProverDal}; use zksync_prover_fri_types::{ circuit_definitions::{ boojum::field::goldilocks::GoldilocksField, - circuit_definitions::{ - aux_layer::{ - wrapper::ZkSyncCompressionWrapper, ZkSyncCompressionForWrapperCircuit, - ZkSyncCompressionLayerCircuit, ZkSyncCompressionProof, - ZkSyncCompressionProofForWrapper, ZkSyncCompressionVerificationKeyForWrapper, - }, - recursion_layer::{ - ZkSyncRecursionLayerProof, ZkSyncRecursionLayerStorageType, - ZkSyncRecursionVerificationKey, - }, - }, + circuit_definitions::recursion_layer::ZkSyncRecursionLayerProof, zkevm_circuits::scheduler::block_header::BlockAuxilaryOutputWitness, }, get_current_pod_name, AuxOutputWitnessWrapper, FriProofWrapper, @@ -39,23 +26,16 @@ use crate::metrics::METRICS; pub struct ProofCompressor { blob_store: Arc, pool: ConnectionPool, - compression_mode: u8, max_attempts: u32, protocol_version: ProtocolSemanticVersion, keystore: Keystore, is_fflonk: bool, } -pub enum Proof { - Plonk(Box), - Fflonk(FflonkSnarkVerifierCircuitProof), -} - impl ProofCompressor { pub fn new( blob_store: Arc, pool: ConnectionPool, - compression_mode: u8, max_attempts: u32, protocol_version: ProtocolSemanticVersion, keystore: Keystore, @@ -64,7 +44,6 @@ impl ProofCompressor { Self { blob_store, pool, - compression_mode, max_attempts, protocol_version, keystore, @@ -85,151 +64,6 @@ impl ProofCompressor { } array } - - #[tracing::instrument(skip(proof, _compression_mode))] - pub fn generate_plonk_proof( - proof: ZkSyncRecursionLayerProof, - _compression_mode: u8, - keystore: Keystore, - ) -> anyhow::Result { - let scheduler_vk = keystore - .load_recursive_layer_verification_key( - ZkSyncRecursionLayerStorageType::SchedulerCircuit as u8, - ) - .context("get_recursiver_layer_vk_for_circuit_type()")?; - - let wrapper_proof = { - let crs = get_trusted_setup(); - let wrapper_config = DEFAULT_WRAPPER_CONFIG; - let mut prover = WrapperProver::::new(&crs, wrapper_config).unwrap(); - - prover - .generate_setup_data(scheduler_vk.into_inner()) - .unwrap(); - prover.generate_proofs(proof.into_inner()).unwrap(); - - prover.get_wrapper_proof().unwrap() - }; - - // (Re)serialization should always succeed. - let serialized = bincode::serialize(&wrapper_proof) - .expect("Failed to serialize proof with ZkSyncSnarkWrapperCircuit"); - - // For sending to L1, we can use the `FinalProof` type, that has a generic circuit inside, that is not used for serialization. - // So `FinalProof` and `Proof>>` are compatible on serialization bytecode level. - let final_proof: FinalProof = - bincode::deserialize(&serialized).expect("Failed to deserialize final proof"); - Ok(final_proof) - } - - #[tracing::instrument(skip(proof, compression_mode, keystore))] - pub fn generate_fflonk_proof( - proof: ZkSyncRecursionLayerProof, - compression_mode: u8, - keystore: Keystore, - ) -> anyhow::Result { - let scheduler_vk = keystore - .load_recursive_layer_verification_key( - ZkSyncRecursionLayerStorageType::SchedulerCircuit as u8, - ) - .context("get_recursiver_layer_vk_for_circuit_type()")?; - - // compress proof step by step: 1 -> 2 -> 3 -> 4 -> 5(wrapper) - let (compression_wrapper_proof, compression_wrapper_vk) = Self::compress_proof( - &keystore, - proof.into_inner(), - scheduler_vk.into_inner(), - compression_mode, - )?; - - // construct fflonk snark verifier circuit - let wrapper_function = - ZkSyncCompressionWrapper::from_numeric_circuit_type(compression_mode); - let fixed_parameters = compression_wrapper_vk.fixed_parameters.clone(); - let circuit = FflonkSnarkVerifierCircuit { - witness: Some(compression_wrapper_proof), - vk: compression_wrapper_vk, - fixed_parameters, - transcript_params: (), - wrapper_function, - }; - - tracing::info!("Proving FFLONK snark verifier"); - - let setup = keystore.load_fflonk_snark_verifier_setup_data()?; - - tracing::info!("Loaded setup data for FFLONK verification"); - - let proof = fflonk_gpu::gpu_prove_fflonk_snark_verifier_circuit_with_precomputation( - &circuit, - &setup, - &setup.get_verification_key(), - ); - tracing::info!("Finished proof generation"); - Ok(proof) - } - - pub fn compress_proof( - keystore: &Keystore, - proof: ZkSyncCompressionProof, - vk: ZkSyncRecursionVerificationKey, - compression_steps: u8, - ) -> anyhow::Result<( - ZkSyncCompressionProofForWrapper, - ZkSyncCompressionVerificationKeyForWrapper, - )> { - let worker = franklin_crypto::boojum::worker::Worker::new(); - let mut compression_circuit = - ZkSyncCompressionLayerCircuit::from_witness_and_vk(Some(proof), vk.clone(), 1); - let mut compression_wrapper_circuit = None; - - for step_idx in 1..compression_steps { - tracing::info!("Proving compression {:?}", step_idx); - let setup_data = keystore.load_compression_setup_data(step_idx)?; - let (proof, vk) = - proof_compression_gpu::prove_compression_layer_circuit_with_precomputations( - compression_circuit.clone(), - &setup_data.setup, - setup_data.finalization_hint, - setup_data.vk, - &worker, - ); - tracing::info!("Proof for compression {:?} is generated!", step_idx); - - if step_idx + 1 == compression_steps { - compression_wrapper_circuit = - Some(ZkSyncCompressionForWrapperCircuit::from_witness_and_vk( - Some(proof), - vk, - compression_steps, - )); - } else { - compression_circuit = ZkSyncCompressionLayerCircuit::from_witness_and_vk( - Some(proof), - vk, - step_idx + 1, - ); - } - } - - // last wrapping step - tracing::info!("Proving compression {} for wrapper", compression_steps); - - let setup_data = keystore.load_compression_wrapper_setup_data(compression_steps)?; - let (proof, vk) = - proof_compression_gpu::prove_compression_wrapper_circuit_with_precomputations( - compression_wrapper_circuit.unwrap(), - &setup_data.setup, - setup_data.finalization_hint, - setup_data.vk, - &worker, - ); - tracing::info!( - "Proof for compression wrapper {} is generated!", - compression_steps - ); - Ok((proof, vk)) - } } #[async_trait] @@ -237,7 +71,7 @@ impl JobProcessor for ProofCompressor { type Job = ZkSyncRecursionLayerProof; type JobId = L1BatchNumber; - type JobArtifacts = Proof; + type JobArtifacts = SnarkWrapperProof; const SERVICE_NAME: &'static str = "ProofCompressor"; @@ -292,23 +126,19 @@ impl JobProcessor for ProofCompressor { job: ZkSyncRecursionLayerProof, _started_at: Instant, ) -> JoinHandle> { - let compression_mode = self.compression_mode; let keystore = self.keystore.clone(); - let is_fflonk = self.is_fflonk; + let snark_wrapper_mode = if self.is_fflonk { + SnarkWrapper::FFfonk + } else { + SnarkWrapper::Plonk + }; + tokio::task::spawn_blocking(move || { - if !is_fflonk { - Ok(Proof::Plonk(Box::new(Self::generate_plonk_proof( - job, - compression_mode, - keystore, - )?))) - } else { - Ok(Proof::Fflonk(Self::generate_fflonk_proof( - job, - compression_mode, - keystore, - )?)) - } + Ok(run_proof_chain( + snark_wrapper_mode, + &keystore, + job.into_inner(), + )) }) } @@ -333,16 +163,18 @@ impl JobProcessor for ProofCompressor { Self::aux_output_witness_to_array(aux_output_witness_wrapper.0); let l1_batch_proof = match artifacts { - Proof::Plonk(proof) => L1BatchProofForL1::Plonk(PlonkL1BatchProofForL1 { - aggregation_result_coords, - scheduler_proof: *proof, - protocol_version: self.protocol_version, - }), - Proof::Fflonk(proof) => L1BatchProofForL1::Fflonk(FflonkL1BatchProofForL1 { + SnarkWrapperProof::Plonk(proof) => L1BatchProofForL1::Plonk(PlonkL1BatchProofForL1 { aggregation_result_coords, scheduler_proof: proof, protocol_version: self.protocol_version, }), + SnarkWrapperProof::FFfonk(proof) => { + L1BatchProofForL1::Fflonk(FflonkL1BatchProofForL1 { + aggregation_result_coords, + scheduler_proof: proof, + protocol_version: self.protocol_version, + }) + } }; let blob_save_started_at = Instant::now(); diff --git a/prover/crates/bin/proof_fri_compressor/src/main.rs b/prover/crates/bin/proof_fri_compressor/src/main.rs index dae03ab41465..bb46c1e7cb75 100644 --- a/prover/crates/bin/proof_fri_compressor/src/main.rs +++ b/prover/crates/bin/proof_fri_compressor/src/main.rs @@ -1,7 +1,7 @@ #![allow(incomplete_features)] // We have to use generic const exprs. #![feature(generic_const_exprs)] -use std::{env, time::Duration}; +use std::time::Duration; use anyhow::Context as _; use clap::Parser; @@ -96,7 +96,6 @@ async fn main() -> anyhow::Result<()> { let proof_compressor = ProofCompressor::new( blob_store, pool, - config.compression_mode, config.max_attempts, protocol_version, keystore, @@ -114,7 +113,7 @@ async fn main() -> anyhow::Result<()> { }) .expect("Error setting Ctrl+C handler"); // Setting handler should always succeed. - setup_crs_keys(&config, is_fflonk); + setup_crs_keys(&config); tracing::info!("Starting proof compressor"); @@ -139,20 +138,10 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn setup_crs_keys(config: &FriProofCompressorConfig, is_fflonk: bool) { - if is_fflonk { - download_initial_setup_keys_if_not_present( - &config.universal_fflonk_setup_path, - &config.universal_fflonk_setup_download_url, - ); - - env::set_var("COMPACT_CRS_FILE", &config.universal_fflonk_setup_path); - return; - } - +fn setup_crs_keys(config: &FriProofCompressorConfig) { download_initial_setup_keys_if_not_present( &config.universal_setup_path, &config.universal_setup_download_url, ); - env::set_var("CRS_FILE", &config.universal_setup_path); + std::env::set_var("COMPACT_CRS_FILE", &config.universal_setup_path); } diff --git a/prover/crates/bin/vk_setup_data_generator_server_fri/Cargo.toml b/prover/crates/bin/vk_setup_data_generator_server_fri/Cargo.toml index f385c33dd6ad..895d43ae42b7 100644 --- a/prover/crates/bin/vk_setup_data_generator_server_fri/Cargo.toml +++ b/prover/crates/bin/vk_setup_data_generator_server_fri/Cargo.toml @@ -40,4 +40,4 @@ proptest.workspace = true [features] default = [] -gpu = ["zksync_prover_keystore/gpu", "proof-compression-gpu", "shivini"] +gpu = ["zksync_prover_keystore/gpu", "proof-compression-gpu/allocator", "shivini"] diff --git a/prover/crates/bin/vk_setup_data_generator_server_fri/src/main.rs b/prover/crates/bin/vk_setup_data_generator_server_fri/src/main.rs index edd88846d1bc..3e5370b2c888 100644 --- a/prover/crates/bin/vk_setup_data_generator_server_fri/src/main.rs +++ b/prover/crates/bin/vk_setup_data_generator_server_fri/src/main.rs @@ -1,6 +1,5 @@ -#![feature(allocator_api)] -#![allow(dead_code)] // todo: remove after setup is generated -#![allow(unused_imports)] // todo: remove after setup is generated +#![feature(allocator_api, generic_const_exprs)] +#![allow(incomplete_features)] //! Tool to generate different types of keys used by the proving system. //! @@ -12,60 +11,41 @@ use clap::{Parser, Subcommand}; use commitment_generator::read_and_update_contract_toml; use indicatif::{ProgressBar, ProgressStyle}; #[cfg(feature = "gpu")] -use shivini::ProverContext; +use proof_compression_gpu::{ + precompute_proof_chain_with_fflonk, precompute_proof_chain_with_plonk, BlobStorageExt, +}; use tracing::level_filters::LevelFilter; use zkevm_test_harness::{ - boojum::worker::Worker, compute_setups::{ basic_vk_count, generate_base_layer_vks, generate_recursive_layer_vks, recursive_layer_vk_count, }, - data_source::{in_memory_data_source::InMemoryDataSource, SetupDataSource}, - proof_wrapper_utils::{ - check_trusted_setup_file_existace, get_wrapper_setup_and_vk_from_scheduler_vk, - WrapperConfig, - }, -}; -use zksync_prover_fri_types::{ - circuit_definitions::circuit_definitions::recursion_layer::ZkSyncRecursionLayerStorageType, - ProverServiceDataKey, + data_source::in_memory_data_source::InMemoryDataSource, }; -#[cfg(feature = "gpu")] -use zksync_prover_keystore::setup_data_generator::get_fflonk_snark_verifier_setup_and_vk; +use zksync_prover_fri_types::ProverServiceDataKey; use zksync_prover_keystore::{ keystore::Keystore, setup_data_generator::{CPUSetupDataGenerator, GPUSetupDataGenerator, SetupDataGenerator}, }; -#[cfg(feature = "gpu")] -use crate::utils::{ - generate_compression_for_wrapper_vks, generate_compression_vks, - get_plonk_wrapper_setup_and_vk_from_scheduler_vk, -}; - mod commitment_generator; -mod utils; mod vk_commitment_helper; #[cfg(test)] mod tests; + /// Generates new verification keys, and stores them in `keystore`. /// Jobs describe how many generators can run in parallel (each one is around 30 GB). /// If quiet is true, it doesn't display any progress bar. fn generate_vks(keystore: &Keystore, jobs: usize, quiet: bool) -> anyhow::Result<()> { - // Start by checking the trusted setup existence. - // This is used at the last step, but we want to fail early if user didn't configure everything - // correctly. - check_trusted_setup_file_existace(); - let progress_bar = if quiet { None } else { let count = basic_vk_count() + recursive_layer_vk_count() + 2; let progress_bar = ProgressBar::new(count as u64); progress_bar.set_style(ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos:>7}/{len:7} ({eta})") - .progress_chars("#>-")); + .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos:>7}/{len:7} ({eta})") + .progress_chars("#>-")); Some(progress_bar) }; @@ -89,66 +69,7 @@ fn generate_vks(keystore: &Keystore, jobs: usize, quiet: bool) -> anyhow::Result }) .map_err(|err| anyhow::anyhow!("Failed generating recursive vk's: {err}"))?; - #[cfg(feature = "gpu")] - { - let config = WrapperConfig::new(5); - let worker = Worker::new(); - - tracing::info!("Creating prover context"); - - let _context = ProverContext::create().context("failed initializing gpu prover context")?; - tracing::info!("Generating verification keys for compression layers."); - generate_compression_vks(config, &mut in_memory_source, &worker); - - tracing::info!("Generating verification keys for compression for wrapper."); - - generate_compression_for_wrapper_vks(config, &mut in_memory_source, &worker); - - tracing::info!("Saving keys & hints"); - } - - keystore.save_keys_from_data_source(&in_memory_source)?; - - // Generate snark VK - let scheduler_vk = in_memory_source - .get_recursion_layer_vk(ZkSyncRecursionLayerStorageType::SchedulerCircuit as u8) - .map_err(|err| anyhow::anyhow!("Failed to get scheduler vk: {err}"))?; - - tracing::info!("Generating PLONK verification keys for snark wrapper."); - - let (_, plonk_vk) = - get_wrapper_setup_and_vk_from_scheduler_vk(scheduler_vk.clone(), WrapperConfig::new(1)); - - keystore - .save_snark_verification_key(plonk_vk) - .context("save_plonk_snark_vk")?; - - if let Some(p) = pb.lock().unwrap().as_ref() { - p.inc(1) - } - - tracing::info!("PLONK vk is generated"); - - #[cfg(feature = "gpu")] - { - tracing::info!("Generating FFLONK verification keys for snark wrapper."); - - let (_, fflonk_vk) = get_fflonk_snark_verifier_setup_and_vk(&mut in_memory_source); - - keystore - .save_fflonk_snark_verification_key(fflonk_vk) - .context("save_fflonk_snark_vk")?; - - if let Some(p) = pb.lock().unwrap().as_ref() { - p.inc(1) - } - - tracing::info!("FFLONK vk is generated"); - } - - // Let's also update the commitments file. - let commitments = keystore.generate_commitments()?; - keystore.save_commitments(&commitments) + keystore.save_keys_from_data_source(&in_memory_source) } #[derive(Debug, Parser)] @@ -171,9 +92,6 @@ enum CircuitSelector { Recursive, /// Select circuits from basic group. Basic, - Compression, - CompressionWrapper, - Snark, } #[derive(Debug, Parser)] @@ -215,6 +133,10 @@ enum Command { #[arg(long)] quiet: bool, }, + #[command(name = "generate-compressor-data")] + GenerateCompressorPrecomputations, + #[command(name = "generate-crs")] + GenerateCompactCrs, /// Generates setup keys (used by the CPU prover). #[command(name = "generate-sk")] GenerateSetupKeys { @@ -280,17 +202,6 @@ fn generate_setup_keys( .numeric_circuit .expect("--numeric-circuit must be provided"), ), - CircuitSelector::Compression => ProverServiceDataKey::new_compression( - options - .numeric_circuit - .expect("--numeric-circuit must be provided"), - ), - CircuitSelector::CompressionWrapper => ProverServiceDataKey::new_compression_wrapper( - options - .numeric_circuit - .expect("--numeric-circuit must be provided"), - ), - CircuitSelector::Snark => ProverServiceDataKey::snark(), }; let digest = generator @@ -325,7 +236,6 @@ fn main() -> anyhow::Result<()> { read_and_update_contract_toml(&keystore, dryrun) } - Command::GenerateSetupKeys { options } => { let generator = CPUSetupDataGenerator { keystore: keystore_from_optional_path( @@ -344,5 +254,44 @@ fn main() -> anyhow::Result<()> { }; generate_setup_keys(&generator, &options) } + Command::GenerateCompressorPrecomputations => { + #[cfg(not(feature = "gpu"))] + { + anyhow::bail!("Must compile with --gpu feature to use this option.") + } + #[cfg(feature = "gpu")] + { + let keystore = Keystore::locate(); + precompute_proof_chain_with_plonk(&keystore); + precompute_proof_chain_with_fflonk(&keystore); + + let commitments = keystore.generate_commitments()?; + keystore.save_commitments(&commitments) + } + } + Command::GenerateCompactCrs => { + #[cfg(not(feature = "gpu"))] + { + anyhow::bail!("Must compile with --gpu feature to use this option.") + } + #[cfg(feature = "gpu")] + { + let keystore = Keystore::locate(); + + if std::env::var("COMPACT_CRS_FILE").is_err() { + return Err(anyhow::anyhow!("COMPACT_CRS_FILE env variable is not set")); + } + + if std::env::var("IGNITION_TRANSCRIPT_PATH").is_err() { + return Err(anyhow::anyhow!( + "IGNITION_TRANSCRIPT_PATH env variable is not set" + )); + } + + Ok(proof_compression_gpu::create_compact_raw_crs( + keystore.write_compact_raw_crs(), + )) + } + } } } diff --git a/prover/crates/bin/vk_setup_data_generator_server_fri/src/utils.rs b/prover/crates/bin/vk_setup_data_generator_server_fri/src/utils.rs deleted file mode 100644 index 85ce0b5be0b6..000000000000 --- a/prover/crates/bin/vk_setup_data_generator_server_fri/src/utils.rs +++ /dev/null @@ -1,141 +0,0 @@ -use circuit_definitions::{ - boojum::worker::Worker, - circuit_definitions::{ - aux_layer::{ - ZkSyncCompressionForWrapperCircuit, ZkSyncCompressionLayerCircuit, - ZkSyncCompressionLayerStorage, ZkSyncSnarkWrapperSetup, ZkSyncSnarkWrapperVK, - }, - recursion_layer::{ZkSyncRecursionLayerStorageType, ZkSyncRecursionLayerVerificationKey}, - }, -}; -#[cfg(feature = "gpu")] -use shivini::cs::gpu_setup_and_vk_from_base_setup_vk_params_and_hints; -use zkevm_test_harness::{ - data_source::{BlockDataSource, SetupDataSource}, - proof_wrapper_utils::{ - check_trusted_setup_file_existace, get_vk_for_previous_circuit, - get_wrapper_setup_and_vk_from_compression_vk, WrapperConfig, - }, - prover_utils::light::{ - create_light_compression_for_wrapper_setup_data, create_light_compression_layer_setup_data, - }, -}; - -#[cfg(feature = "gpu")] -pub(crate) fn generate_compression_vks( - config: WrapperConfig, - source: &mut DS, - worker: &Worker, -) { - for circuit_type in config.get_compression_types() { - let vk = get_vk_for_previous_circuit(source, circuit_type).unwrap_or_else(|_| { - panic!( - "VK of previous circuit should be present. Current circuit type: {}", - circuit_type - ) - }); - - let compression_circuit = - ZkSyncCompressionLayerCircuit::from_witness_and_vk(None, vk, circuit_type); - let proof_config = compression_circuit.proof_config_for_compression_step(); - - let (setup_base, vk_geometry, vars_hint, witness_hint, finalization_hint) = - create_light_compression_layer_setup_data( - compression_circuit, - worker, - proof_config.fri_lde_factor, - proof_config.merkle_tree_cap_size, - ); - - let (_, vk) = gpu_setup_and_vk_from_base_setup_vk_params_and_hints( - setup_base, - vk_geometry, - vars_hint.clone(), - witness_hint, - worker, - ) - .expect("failed creating GPU compression layer setup data"); - - source - .set_compression_vk(ZkSyncCompressionLayerStorage::from_inner( - circuit_type, - vk.clone(), - )) - .unwrap(); - source - .set_compression_hint(ZkSyncCompressionLayerStorage::from_inner( - circuit_type, - finalization_hint.clone(), - )) - .unwrap(); - } -} - -#[cfg(feature = "gpu")] -pub(crate) fn generate_compression_for_wrapper_vks( - config: WrapperConfig, - source: &mut DS, - worker: &Worker, -) { - let compression_for_wrapper_type = config.get_compression_for_wrapper_type(); - let vk = get_vk_for_previous_circuit(source, compression_for_wrapper_type).unwrap(); - - let circuit = ZkSyncCompressionForWrapperCircuit::from_witness_and_vk( - None, - vk, - compression_for_wrapper_type, - ); - - let proof_config = circuit.proof_config_for_compression_step(); - - let (setup_base, vk_geometry, vars_hint, witness_hint, finalization_hint) = - create_light_compression_for_wrapper_setup_data( - circuit, - worker, - proof_config.fri_lde_factor, - proof_config.merkle_tree_cap_size, - ); - - let (_, vk) = gpu_setup_and_vk_from_base_setup_vk_params_and_hints( - setup_base, - vk_geometry, - vars_hint.clone(), - witness_hint, - worker, - ) - .expect("failed creating GPU compression for wrapper layer setup data"); - - source - .set_compression_for_wrapper_vk(ZkSyncCompressionLayerStorage::from_inner( - compression_for_wrapper_type, - vk.clone(), - )) - .unwrap(); - source - .set_compression_for_wrapper_hint(ZkSyncCompressionLayerStorage::from_inner( - compression_for_wrapper_type, - finalization_hint.clone(), - )) - .unwrap(); -} - -/// Computes wrapper vk from scheduler vk -/// We store all vks in the RAM -pub fn get_plonk_wrapper_setup_and_vk_from_scheduler_vk( - source: &mut DS, - vk: ZkSyncRecursionLayerVerificationKey, - config: WrapperConfig, -) -> (ZkSyncSnarkWrapperSetup, ZkSyncSnarkWrapperVK) { - // Check trusted setup file for later - check_trusted_setup_file_existace(); - - // Check circuit type correctness - assert_eq!( - vk.numeric_circuit_type(), - ZkSyncRecursionLayerStorageType::SchedulerCircuit as u8 - ); - - let wrapper_type = config.get_wrapper_type(); - let wrapper_vk = source.get_compression_for_wrapper_vk(wrapper_type).unwrap(); - get_wrapper_setup_and_vk_from_compression_vk(wrapper_vk, config) -} diff --git a/prover/crates/lib/keystore/Cargo.toml b/prover/crates/lib/keystore/Cargo.toml index a247a24bdd8b..2c21067f2428 100644 --- a/prover/crates/lib/keystore/Cargo.toml +++ b/prover/crates/lib/keystore/Cargo.toml @@ -17,6 +17,7 @@ zkevm_test_harness.workspace = true circuit_definitions = { workspace = true, features = ["log_tracing"] } shivini = { workspace = true, optional = true } fflonk-gpu = { workspace = true, optional = true } +proof-compression-gpu = { workspace = true, optional = true } fflonk.workspace = true boojum-cuda = { workspace = true, optional = true } @@ -37,4 +38,4 @@ futures = { workspace = true, features = ["compat"] } default = [] # feature to not compile era-bellman-cuda, but to be able to use GPU features gpu-light = ["dep:shivini", "dep:boojum-cuda"] -gpu = ["dep:shivini", "dep:fflonk-gpu", "dep:boojum-cuda"] +gpu = ["dep:shivini", "dep:fflonk-gpu", "dep:boojum-cuda", "dep:proof-compression-gpu"] diff --git a/prover/crates/lib/keystore/src/compressor.rs b/prover/crates/lib/keystore/src/compressor.rs new file mode 100644 index 000000000000..ea4518238fd6 --- /dev/null +++ b/prover/crates/lib/keystore/src/compressor.rs @@ -0,0 +1,221 @@ +use std::{ + fs::File, + io::{Read, Write}, +}; + +use circuit_definitions::circuit_definitions::recursion_layer::ZkSyncRecursionLayerStorageType; +use zksync_prover_fri_types::ProverServiceDataKey; + +use crate::keystore::{Keystore, ProverServiceDataType}; + +const COMPACT_CRS_ENV_VAR: &str = "COMPACT_CRS_FILE"; + +impl proof_compression_gpu::BlobStorage for Keystore { + fn read_scheduler_vk(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_recursive( + ZkSyncRecursionLayerStorageType::SchedulerCircuit as u8, + ), + ProverServiceDataType::VerificationKey, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compression_layer_finalization_hint(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression(circuit_id), + ProverServiceDataType::FinalizationHints, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compression_layer_vk(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression(circuit_id), + ProverServiceDataType::VerificationKey, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compression_layer_precomputation(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression(circuit_id), + ProverServiceDataType::SetupData, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compression_wrapper_finalization_hint(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression_wrapper(circuit_id), + ProverServiceDataType::FinalizationHints, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compression_wrapper_vk(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression_wrapper(circuit_id), + ProverServiceDataType::VerificationKey, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compression_wrapper_precomputation( + &self, + circuit_id: u8, + ) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression_wrapper(circuit_id), + ProverServiceDataType::SetupData, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_fflonk_vk(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::FflonkSnarkVerificationKey, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_fflonk_precomputation(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::FflonkSetupData, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_plonk_vk(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::SnarkVerificationKey, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_plonk_precomputation(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::PlonkSetupData, + ); + + Box::new(File::open(filepath).unwrap()) + } + + fn read_compact_raw_crs(&self) -> Box { + let filepath = + std::env::var(COMPACT_CRS_ENV_VAR).expect("No compact CRS file path provided"); + Box::new(File::open(filepath).unwrap()) + } +} + +impl proof_compression_gpu::BlobStorageExt for Keystore { + fn write_compression_layer_finalization_hint(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression(circuit_id), + ProverServiceDataType::FinalizationHints, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_compression_layer_vk(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression(circuit_id), + ProverServiceDataType::VerificationKey, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_compression_layer_precomputation(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression(circuit_id), + ProverServiceDataType::SetupData, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_compression_wrapper_finalization_hint(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression_wrapper(circuit_id), + ProverServiceDataType::FinalizationHints, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_compression_wrapper_vk(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression_wrapper(circuit_id), + ProverServiceDataType::VerificationKey, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_compression_wrapper_precomputation(&self, circuit_id: u8) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::new_compression_wrapper(circuit_id), + ProverServiceDataType::SetupData, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_fflonk_vk(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::FflonkSnarkVerificationKey, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_fflonk_precomputation(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::FflonkSetupData, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_plonk_vk(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::SnarkVerificationKey, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_plonk_precomputation(&self) -> Box { + let filepath = self.get_file_path( + ProverServiceDataKey::snark(), + ProverServiceDataType::PlonkSetupData, + ); + + Box::new(File::create(filepath).unwrap()) + } + + fn write_compact_raw_crs(&self) -> Box { + let filepath = + std::env::var(COMPACT_CRS_ENV_VAR).expect("No compact CRS file path provided"); + Box::new(File::create(filepath).unwrap()) + } +} diff --git a/prover/crates/lib/keystore/src/keystore.rs b/prover/crates/lib/keystore/src/keystore.rs index c4ffb32ce73c..eec7b4c8344e 100644 --- a/prover/crates/lib/keystore/src/keystore.rs +++ b/prover/crates/lib/keystore/src/keystore.rs @@ -41,6 +41,8 @@ pub enum ProverServiceDataType { VerificationKey, SetupData, FinalizationHints, + PlonkSetupData, + FflonkSetupData, SnarkVerificationKey, FflonkSnarkVerificationKey, } @@ -114,7 +116,7 @@ impl Keystore { &self.basedir } - fn get_file_path( + pub(crate) fn get_file_path( &self, key: ProverServiceDataKey, service_data_type: ProverServiceDataType, @@ -130,6 +132,12 @@ impl Keystore { ProverServiceDataType::FinalizationHints => self .basedir .join(format!("finalization_hints_{}.bin", name)), + ProverServiceDataType::PlonkSetupData => { + self.basedir.join(format!("plonk_setup_{}_data.bin", name)) + } + ProverServiceDataType::FflonkSetupData => { + self.basedir.join(format!("fflonk_setup_{}_data.bin", name)) + } ProverServiceDataType::SnarkVerificationKey => { self.basedir.join(format!("verification_{}_key.json", name)) } @@ -187,6 +195,14 @@ impl Keystore { &self, circuit_type: u8, ) -> anyhow::Result { + if circuit_type == ZkSyncRecursionLayerStorageType::SchedulerCircuit as u8 { + let vk = Self::load_json_from_file(self.get_file_path( + ProverServiceDataKey::new_recursive(circuit_type), + ProverServiceDataType::VerificationKey, + ))?; + return Ok(ZkSyncRecursionLayerVerificationKey::SchedulerCircuit(vk)); + } + Self::load_json_from_file(self.get_file_path( ProverServiceDataKey::new_recursive(circuit_type), ProverServiceDataType::VerificationKey, @@ -197,20 +213,43 @@ impl Keystore { &self, circuit_type: u8, ) -> anyhow::Result { - Self::load_json_from_file(self.get_file_path( + let key = Self::load_json_from_file(self.get_file_path( ProverServiceDataKey::new_compression(circuit_type), ProverServiceDataType::VerificationKey, - )) + ))?; + + match circuit_type { + 1 => Ok(ZkSyncCompressionLayerVerificationKey::CompressionMode1Circuit(key)), + 2 => Ok(ZkSyncCompressionLayerVerificationKey::CompressionMode2Circuit(key)), + 3 => Ok(ZkSyncCompressionLayerVerificationKey::CompressionMode3Circuit(key)), + 4 => Ok(ZkSyncCompressionLayerVerificationKey::CompressionMode4Circuit(key)), + _ => Err(anyhow::anyhow!( + "Invalid compression circuit type: {}", + circuit_type + )), + } } pub fn load_compression_for_wrapper_vk( &self, circuit_type: u8, ) -> anyhow::Result { - Self::load_json_from_file(self.get_file_path( + let key = Self::load_json_from_file(self.get_file_path( ProverServiceDataKey::new_compression_wrapper(circuit_type), ProverServiceDataType::VerificationKey, - )) + ))?; + + match circuit_type { + 1 => Ok(ZkSyncCompressionForWrapperVerificationKey::CompressionMode1Circuit(key)), + 2 => Ok(ZkSyncCompressionForWrapperVerificationKey::CompressionMode2Circuit(key)), + 3 => Ok(ZkSyncCompressionForWrapperVerificationKey::CompressionMode3Circuit(key)), + 4 => Ok(ZkSyncCompressionForWrapperVerificationKey::CompressionMode4Circuit(key)), + 5 => Ok(ZkSyncCompressionForWrapperVerificationKey::CompressionMode5Circuit(key)), + _ => Err(anyhow::anyhow!( + "Invalid compression circuit type: {}", + circuit_type + )), + } } pub fn save_base_layer_verification_key( @@ -233,6 +272,12 @@ impl Keystore { ProverServiceDataKey::new_recursive(vk.numeric_circuit_type()), ProverServiceDataType::VerificationKey, ); + + if let ZkSyncRecursionLayerVerificationKey::SchedulerCircuit(key) = vk { + tracing::info!("saving recursive layer verification key to: {:?}", filepath); + return Self::save_json_pretty(filepath, &key); + } + tracing::info!("saving recursive layer verification key to: {:?}", filepath); Self::save_json_pretty(filepath, &vk) } @@ -249,7 +294,7 @@ impl Keystore { "saving compression layer verification key to: {:?}", filepath ); - Self::save_json_pretty(filepath, &vk) + Self::save_json_pretty(filepath, &vk.into_inner()) } pub fn save_compression_for_wrapper_vk( @@ -264,7 +309,7 @@ impl Keystore { "saving compression wrapper verification key to: {:?}", filepath ); - Self::save_json_pretty(filepath, &vk) + Self::save_json_pretty(filepath, &vk.into_inner()) } /// @@ -645,45 +690,6 @@ impl Keystore { ) .context("save_finalization_hints()")?; - // Compression - // todo: don't use hardcoded values - for circuit in 1..5 { - let vk = source - .get_compression_vk(circuit as u8) - .map_err(|err| anyhow::anyhow!("No vk exist for circuit type: {circuit}: {err}"))?; - - self.save_compression_vk(vk) - .context("save_compression_vk()")?; - - let hint = source.get_compression_hint(circuit as u8).map_err(|err| { - anyhow::anyhow!("No finalization hint exist for circuit type: {circuit}: {err}") - })?; - - self.save_finalization_hints( - ProverServiceDataKey::new_compression(circuit as u8), - &hint.into_inner(), - ) - .context("save_finalization_hints()")?; - } - - // Compression wrapper - let vk = source - .get_compression_for_wrapper_vk(5) - .map_err(|err| anyhow::anyhow!("No vk exist for circuit type: 5: {err}"))?; - - self.save_compression_for_wrapper_vk(vk) - .context("save_compression_wrapper_vk()")?; - - let hint = source.get_compression_for_wrapper_hint(5).map_err(|err| { - anyhow::anyhow!("No finalization hint exist for circuit type: 5: {err}") - })?; - - self.save_finalization_hints( - ProverServiceDataKey::new_compression_wrapper(5), - &hint.into_inner(), - ) - .context("save_finalization_hints()")?; - Ok(()) } diff --git a/prover/crates/lib/keystore/src/lib.rs b/prover/crates/lib/keystore/src/lib.rs index e5f00fd307ba..bc8bbdcd992c 100644 --- a/prover/crates/lib/keystore/src/lib.rs +++ b/prover/crates/lib/keystore/src/lib.rs @@ -28,6 +28,9 @@ pub mod keystore; pub mod setup_data_generator; pub mod utils; +#[cfg(feature = "gpu")] +pub mod compressor; + #[derive(Debug, Serialize, Deserialize)] #[serde( bound = "F: serde::Serialize + serde::de::DeserializeOwned, P: serde::Serialize + serde::de::DeserializeOwned" diff --git a/prover/crates/lib/keystore/src/setup_data_generator.rs b/prover/crates/lib/keystore/src/setup_data_generator.rs index 162d94ced6dd..ae926d17ca3f 100644 --- a/prover/crates/lib/keystore/src/setup_data_generator.rs +++ b/prover/crates/lib/keystore/src/setup_data_generator.rs @@ -7,21 +7,9 @@ use anyhow::Context as _; #[cfg(any(feature = "gpu", feature = "gpu-light"))] use boojum_cuda::poseidon2::GLHasher; #[cfg(any(feature = "gpu", feature = "gpu-light"))] -use circuit_definitions::circuit_definitions::aux_layer::{ - wrapper::ZkSyncCompressionWrapper, CompressionProofsTreeHasherForWrapper, -}; -#[cfg(feature = "gpu")] -use fflonk_gpu::{ - FflonkDeviceSetup, FflonkSnarkVerifierCircuit, FflonkSnarkVerifierCircuitDeviceSetup, - FflonkSnarkVerifierCircuitVK, -}; -#[cfg(any(feature = "gpu", feature = "gpu-light"))] use shivini::cs::gpu_setup_and_vk_from_base_setup_vk_params_and_hints; #[cfg(any(feature = "gpu", feature = "gpu-light"))] -use zkevm_test_harness::{ - compute_setups::light::generate_light_circuit_setup_data, - data_source::in_memory_data_source::InMemoryDataSource, -}; +use zkevm_test_harness::compute_setups::light::generate_light_circuit_setup_data; use zkevm_test_harness::{ compute_setups::{generate_circuit_setup_data, CircuitSetupData}, data_source::SetupDataSource, @@ -55,10 +43,10 @@ pub fn generate_setup_data_common( .into_inner(), ), ProvingStage::Compression => { - unreachable!("Compression stage should not be generated with CPU.") + unreachable!("Compression stage setup data should be generated with a generate-compressor-data command") } ProvingStage::CompressionWrapper => { - unreachable!("CompressionWrapper stage should not be generated with CPU.") + unreachable!("CompressionWrapper stage setup data should be generated with a generate-compressor-data command") } _ => ( Some(keystore.load_finalization_hints(circuit)?), @@ -108,23 +96,9 @@ pub trait SetupDataGenerator { } if circuit == ProverServiceDataKey::snark() { - #[cfg(not(feature = "gpu"))] - { - anyhow::bail!("Must compile with --gpu feature to use this option."); - } - #[cfg(feature = "gpu")] - { - let mut data_source = self.keystore().load_keys_to_data_source()?; - let (setup, _) = get_fflonk_snark_verifier_setup_and_vk(&mut data_source); - if !dry_run { - self.keystore() - .save_fflonk_snark_setup_data(setup) - .context("save_setup_data()")?; - } - return Ok(String::from( - "FFLONK is serialized differently, skipping hashing.", - )); - } + unreachable!( + "Snark setup data should be generated with generate-compressor-data command" + ) } let serialized = self.generate_setup_data(circuit)?; @@ -146,7 +120,7 @@ pub trait SetupDataGenerator { dry_run: bool, recompute_if_missing: bool, ) -> anyhow::Result> { - Ok(ProverServiceDataKey::all() + Ok(ProverServiceDataKey::all_boojum() .iter() .map(|circuit| { tracing::info!("Generating setup data for {:?}", circuit.name()); @@ -206,44 +180,10 @@ impl SetupDataGenerator for GPUSetupDataGenerator { let worker = Worker::new(); match circuit.stage { - ProvingStage::CompressionWrapper => { - let (gpu_setup_data, verification_key) = - gpu_setup_and_vk_from_base_setup_vk_params_and_hints::< - CompressionProofsTreeHasherForWrapper, - _, - >( - circuit_setup_data.setup_base, - circuit_setup_data.vk_geometry, - circuit_setup_data.vars_hint.clone(), - circuit_setup_data.wits_hint, - &worker, - ) - .context("failed creating GPU base layer setup data")?; - - let gpu_prover_setup_data = GpuProverSetupData { - setup: gpu_setup_data, - vk: verification_key.clone(), - finalization_hint: circuit_setup_data.finalization_hint, - }; - - let serialized_vk = get_vk_by_circuit(self.keystore.clone(), circuit)?; - - assert_eq!( - bincode::serialize(&verification_key) - .expect("Failed serializing setup data"), - serialized_vk, - "Verification key mismatch for circuit: {:?}", - circuit.name() - ); - - // Serialization should always succeed. - Ok(bincode::serialize(&gpu_prover_setup_data) - .expect("Failed serializing setup data")) - } - ProvingStage::Snark => { - unreachable!( - "We cannot serialize Fflonk data with bincode, it is done separately" - ) + ProvingStage::CompressionWrapper + | ProvingStage::Snark + | ProvingStage::Compression => { + unreachable!("Setup data for compression, compression-wrapper and snark stages should be generated with generate-compressor-data command") } _ => { let (gpu_setup_data, verification_key) = @@ -320,31 +260,3 @@ fn get_vk_by_circuit(keystore: Keystore, circuit: ProverServiceDataKey) -> anyho } } } - -#[cfg(feature = "gpu")] -pub fn get_fflonk_snark_verifier_setup_and_vk( - data_source: &mut InMemoryDataSource, -) -> ( - FflonkSnarkVerifierCircuitDeviceSetup, - FflonkSnarkVerifierCircuitVK, -) { - let vk = data_source - .get_compression_for_wrapper_vk(5) - .unwrap() - .into_inner(); - let fixed_parameters = vk.fixed_parameters.clone(); - // todo: do not hardcode this value - let wrapper_function = ZkSyncCompressionWrapper::from_numeric_circuit_type(5); - - let circuit = FflonkSnarkVerifierCircuit { - witness: None, - vk, - fixed_parameters, - transcript_params: (), - wrapper_function, - }; - let setup = FflonkDeviceSetup::<_, _, _>::create_setup_on_device(&circuit).unwrap(); - let snark_vk = setup.get_verification_key(); - - (setup, snark_vk) -} diff --git a/prover/crates/lib/prover_fri_types/src/lib.rs b/prover/crates/lib/prover_fri_types/src/lib.rs index 9d7c32c21d73..5b7a54a31052 100644 --- a/prover/crates/lib/prover_fri_types/src/lib.rs +++ b/prover/crates/lib/prover_fri_types/src/lib.rs @@ -294,12 +294,6 @@ impl ProverServiceDataKey { for numeric_circuit in ZkSyncRecursionLayerStorageType::as_iter_u8() { results.push(ProverServiceDataKey::new_recursive(numeric_circuit)) } - - for numeric_circuit in 1..MAX_COMPRESSION_CIRCUITS { - results.push(ProverServiceDataKey::new_compression(numeric_circuit)); - } - results.push(ProverServiceDataKey::new_compression_wrapper(5)); - results } @@ -311,12 +305,6 @@ impl ProverServiceDataKey { } } - pub fn all() -> Vec { - let mut keys = Self::all_boojum(); - keys.push(Self::snark()); - keys - } - pub fn is_base_layer(&self) -> bool { self.stage == ProvingStage::BasicCircuits } diff --git a/prover/data/keys/finalization_hints_compression_1.bin b/prover/data/keys/finalization_hints_compression_1.bin index 8f5cadc55c5763890b983eb7c20f96124a014c28..d71029042a994cd20811d5c2b29f799c5a275379 100644 GIT binary patch literal 373 zcmbVI(F(#a41C{LggyIU+pVC#A|uqTYN4iOYY`Ox-L944gG2BoB-~wcmq!IqkM4#i zL&}=F3dRv$427xQ!y%#Yt^i__M}X~rO;&fQcC)_josCyoIb0~@Jf2n169$KB7?LH< z8@9{(>=?tSdW}|7!erYDnK2)a*7Rt|$1gz^|BYZw7F#7!2@Jr>2(ZfFKN-urK6cFE GtLF=Aiccy4 literal 120 tcmZQ%fB+6C%?zbs)Z}m&ixJFVfpMX9&I$+vBFn%4<1?Yj!_0-r0|4JI1yTS2 diff --git a/prover/data/keys/finalization_hints_compression_2.bin b/prover/data/keys/finalization_hints_compression_2.bin index c6a4253d81e61be5664c6c38d3d1269e0503fd8b..5dc0776779f414ecd38a95d3a32e39acec3caf6b 100644 GIT binary patch literal 367 zcmbV|%?`pK5QOi2icQZRMEt3~ifJ}TQAuziEyP6QyUUL;J;=q&1~N13d^{U~cC>dq zYZ693gx8kvs>x670gh3H&jetLd<9tkcgX2J&OYT^X-&9MWpS~Pb$Ah8P3SF(VThI3 zdeh~2E{uK@QlU{4rH<`aS#sfIv_wrV{J{9O-|jdsoeEE(opII-c}x14nt_)G=Z1vc FyxxU$P7?qC literal 120 pcmZQ%fB+6C%?PDo)FxIaO94W&z$BowuM~s<(+A@-p~*9&@d1K!0*C+r diff --git a/prover/data/keys/finalization_hints_compression_3.bin b/prover/data/keys/finalization_hints_compression_3.bin index ef3f24e18c23dcaff87a7e9715352a2bc3c516c0..f8148a2cb2ee7c6e3bad411a366a757c4f9eee18 100644 GIT binary patch literal 366 zcmbVHK?=e!5WM#lOV1vfwpi*{q%1Lwx)3*^O%N3SZkmV}ErOR}hnZn#?gcl|cj|C8!0<0P8Uxwz>u&gc~Pc`aI A(f|Me literal 120 ncmZQ%fB+6C%?_nu)H)bL0K#B_@u74eKZL=E#%Ds~Go$eVd5i*C diff --git a/prover/data/keys/finalization_hints_compression_4.bin b/prover/data/keys/finalization_hints_compression_4.bin index ca6c08bab1f180057124b5423fface7b6a9ed134..d15a487c3572bdf212cf3d101211548e328cd1a8 100644 GIT binary patch literal 18934 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 A4*&oF literal 2024 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 o-TjBp|MvqDYuU-rA`W9@<#ly>QOnfSlu~9B zLpn+!)@$-Uws6KvVOC}tqMBA{sMNOS#P@d2-aqd1eD3#m&$-Xby_ZayKa>|*G$EAv zUd+OUi)U?jyR2irR$H)967SLoMcxS)lT#deDIs2x}m+NAHB+wTBGUHdxrf&-xf{`ZaZOc*;TT zP^wNlvHruH+VZe(9hM-cIX+a~n$y${@8F*CjlIvm26Olt9n`i{qp)dMQUv7=qY=Iy z*Gpw=uO-H(!VbTw6q0L(d^RSqD`4(S4ZO-YKWbdFcNW$=-V`P2-(?R+L>LIJmIYYC zM_q0hM$%_#uv>O|MnihrJGiLs-)n_mR}#-NBpg>UcwR}&SE8h88L4VC0ri|?_hx(! zj*DSQP3XO#AS$sQN`bcdtW%$e{ZTn%867&x+>d&88r@15mr)H*r&*ug7x?};+*%~1 z^WHYvz~aeF<}w=dDXcfErRkwuI|%n1Xq3CE?KXzTq~AN<(kZ+G%cHJvQbV)9g44E~ zpES6iM$B)7QQ6Glj!RpRtA0o}94v9vhBabx!%Q?UAA_IL=130VEMk91-4aB~Imzpg z@A)OogDs<0!G-+mA}=HU30S?V)vD&wM=mVWVcMz8oOlTzX=Yk3@yzyyB`acz6I>-s z*wM#rl`>n47|(krX}i*z+?B|;P^J!T?~R&-b?=UE8d3@=gePs-59)X9i-2#7g{Jpg z7jJ`0`)E!{y0H`RH>dMbU!MOE35%C^Us>8+9R#;~_sDP7m=Wu3_JUL~YFa;yeCOZ4 z+6B*^EQ3SE`OFL@mm{#KBe7J6n@BuQJj+6lPMs@3Zr#$be5l1=3ahFPrc*Y*Du8JL zlPS6W!Rqh@PeHVP{-_LAYxc8$q;-b_ceC>3#d^~-@Uo;H^R=<-Q(#Wgg}JS=iuLei zy@Qr5O(%%&+bQlv#>A~0DsryRK=X42@)+Fj)b;V_t`uFkE3oq)r&J#Celt=Hg(J36-2`hvfLoiZH;PQrooKV-$LY=49e61v2DGt(Vm z9r^)-49dg+Y?N3!!t9RKg4aA*GwduZT@K^F>l}$5`EzFxxX>1J6+ZXo&m, - #[clap(long)] - pub fflonk_path: Option, - #[clap(long, default_value = "plonk")] - pub compressor_type: CompressorType, -} - -#[derive(Debug, Clone, ValueEnum, EnumIter, strum::Display, PartialEq, Eq, Default)] -pub enum CompressorType { - Fflonk, - #[default] - Plonk, - All, + pub path: Option, } impl CompressorKeysArgs { - pub fn fill_values_with_prompt( - self, - default_plonk_path: &str, - default_fflonk_path: &str, - ) -> CompressorKeysArgs { - let plonk_path = if self.compressor_type != CompressorType::Fflonk { - Some(self.plonk_path.unwrap_or_else(|| { - Prompt::new(MSG_SETUP_COMPRESSOR_KEY_PATH_PROMPT) - .default(default_plonk_path) - .ask() - })) - } else { - None - }; - - let fflonk_path = if self.compressor_type != CompressorType::Plonk { - Some(self.fflonk_path.unwrap_or_else(|| { - Prompt::new(MSG_SETUP_COMPRESSOR_KEY_PATH_PROMPT) - .default(default_fflonk_path) - .ask() - })) - } else { - None - }; + pub fn fill_values_with_prompt(self, default_path: &str) -> CompressorKeysArgs { + let path = self.path.unwrap_or_else(|| { + Prompt::new(MSG_SETUP_COMPRESSOR_KEY_PATH_PROMPT) + .default(default_path) + .ask() + }); - CompressorKeysArgs { - plonk_path, - fflonk_path, - ..self - } + CompressorKeysArgs { path: Some(path) } } } diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs b/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs index afe57a8f5eb4..4956a23ac987 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs @@ -202,16 +202,13 @@ impl ProverInitArgs { pub(crate) fn fill_values_with_prompt( &self, shell: &Shell, - default_plonk_key_path: &str, - default_fflonk_key_path: &str, + default_compressor_key_path: &str, chain_config: &ChainConfig, ) -> anyhow::Result { let proof_store = self.fill_proof_storage_values_with_prompt(shell)?; let public_store = self.fill_public_storage_values_with_prompt(shell)?; - let compressor_key_args = self.fill_setup_compressor_key_values_with_prompt( - default_plonk_key_path, - default_fflonk_key_path, - ); + let compressor_key_args = + self.fill_setup_compressor_key_values_with_prompt(default_compressor_key_path); let bellman_cuda_config = self.fill_bellman_cuda_values_with_prompt(); let cloud_type = self.get_cloud_type_with_prompt(); let database_config = self.fill_database_values_with_prompt(chain_config); @@ -358,14 +355,11 @@ impl ProverInitArgs { fn fill_setup_compressor_key_values_with_prompt( &self, - default_plonk_path: &str, - default_fflonk_path: &str, + default_path: &str, ) -> Option { if self.dev { return Some(CompressorKeysArgs { - plonk_path: Some(default_plonk_path.to_string()), - fflonk_path: Some(default_fflonk_path.to_string()), - ..self.compressor_keys_args.clone() + path: Some(default_path.to_string()), }); } @@ -379,7 +373,7 @@ impl ProverInitArgs { Some( self.compressor_keys_args .clone() - .fill_values_with_prompt(default_plonk_path, default_fflonk_path), + .fill_values_with_prompt(default_path), ) } else { None diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs b/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs index 19ba634a4075..88eec0688da7 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs @@ -3,7 +3,7 @@ use xshell::Shell; use zkstack_cli_common::{logger, spinner::Spinner}; use zkstack_cli_config::{get_link_to_prover, EcosystemConfig, GeneralConfig}; -use super::args::compressor_keys::{CompressorKeysArgs, CompressorType}; +use super::args::compressor_keys::CompressorKeysArgs; use crate::messages::{ MSG_CHAIN_NOT_FOUND_ERR, MSG_DOWNLOADING_SETUP_COMPRESSOR_KEY_SPINNER, MSG_PROOF_COMPRESSOR_CONFIG_NOT_FOUND_ERR, MSG_SETUP_KEY_PATH_ERROR, @@ -16,39 +16,12 @@ pub(crate) async fn run(shell: &Shell, args: CompressorKeysArgs) -> anyhow::Resu .context(MSG_CHAIN_NOT_FOUND_ERR)?; let mut general_config = chain_config.get_general_config()?; - let default_plonk_path = get_default_plonk_compressor_keys_path(&ecosystem_config)?; - let default_fflonk_path = get_default_fflonk_compressor_keys_path(&ecosystem_config)?; - let args = args.fill_values_with_prompt(&default_plonk_path, &default_fflonk_path); - - match args.compressor_type { - CompressorType::Fflonk => { - let path = args.clone().fflonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - - download_compressor_key(shell, &mut general_config, CompressorType::Fflonk, &path)?; - } - CompressorType::Plonk => { - let path = args.plonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - - download_compressor_key(shell, &mut general_config, CompressorType::Plonk, &path)?; - } - CompressorType::All => { - let plonk_path = args.clone().plonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - let fflonk_path = args.clone().fflonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - - download_compressor_key( - shell, - &mut general_config, - CompressorType::Fflonk, - &fflonk_path, - )?; - download_compressor_key( - shell, - &mut general_config, - CompressorType::Plonk, - &plonk_path, - )?; - } - } + let default_path = get_default_compressor_keys_path(&ecosystem_config)?; + let args = args.fill_values_with_prompt(&default_path); + + let path = args.path.context(MSG_SETUP_KEY_PATH_ERROR)?; + + download_compressor_key(shell, &mut general_config, &path)?; chain_config.save_general_config(&general_config)?; @@ -58,7 +31,6 @@ pub(crate) async fn run(shell: &Shell, args: CompressorKeysArgs) -> anyhow::Resu pub(crate) fn download_compressor_key( shell: &Shell, general_config: &mut GeneralConfig, - r#type: CompressorType, path: &str, ) -> anyhow::Result<()> { let spinner = Spinner::new(MSG_DOWNLOADING_SETUP_COMPRESSOR_KEY_SPINNER); @@ -68,50 +40,35 @@ pub(crate) fn download_compressor_key( .expect(MSG_PROOF_COMPRESSOR_CONFIG_NOT_FOUND_ERR) .clone(); - let url = match r#type { - CompressorType::Fflonk => { - compressor_config.universal_fflonk_setup_path = path.to_string(); - general_config.proof_compressor_config = Some(compressor_config.clone()); - compressor_config.universal_fflonk_setup_download_url - } - CompressorType::Plonk => { - compressor_config.universal_setup_path = path.to_string(); - general_config.proof_compressor_config = Some(compressor_config.clone()); - compressor_config.universal_setup_download_url - } - _ => unreachable!("Invalid compressor type"), - }; + compressor_config.universal_setup_path = path.to_string(); + general_config.proof_compressor_config = Some(compressor_config.clone()); let path = std::path::Path::new(path); - logger::info(format!("Downloading setup key by URL: {}", url)); + logger::info(format!( + "Downloading setup key by URL: {}", + compressor_config.universal_setup_download_url + )); let client = reqwest::blocking::Client::builder() .timeout(std::time::Duration::from_secs(600)) .build()?; - let response = client.get(url).send()?.bytes()?; + let response = client + .get(compressor_config.universal_setup_download_url) + .send()? + .bytes()?; shell.write_file(path, &response)?; spinner.finish(); Ok(()) } -pub fn get_default_plonk_compressor_keys_path( - ecosystem_config: &EcosystemConfig, -) -> anyhow::Result { - let link_to_prover = get_link_to_prover(ecosystem_config); - let path = link_to_prover.join("keys/setup/setup_2^24.key"); - let string = path.to_str().unwrap(); - - Ok(String::from(string)) -} - -pub fn get_default_fflonk_compressor_keys_path( +pub fn get_default_compressor_keys_path( ecosystem_config: &EcosystemConfig, ) -> anyhow::Result { let link_to_prover = get_link_to_prover(ecosystem_config); - let path = link_to_prover.join("keys/setup/setup_fflonk_compact.key"); + let path = link_to_prover.join("keys/setup/setup_compact.key"); let string = path.to_str().unwrap(); Ok(String::from(string)) diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/init.rs b/zkstack_cli/crates/zkstack/src/commands/prover/init.rs index b318f4924ec3..51034e02a213 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/init.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/init.rs @@ -24,10 +24,7 @@ use super::{ }; use crate::{ commands::prover::{ - args::{compressor_keys::CompressorType, init::ProofStorageFileBacked}, - compressor_keys::{ - get_default_fflonk_compressor_keys_path, get_default_plonk_compressor_keys_path, - }, + args::init::ProofStorageFileBacked, compressor_keys::get_default_compressor_keys_path, }, consts::{PROVER_MIGRATIONS, PROVER_STORE_MAX_RETRIES}, messages::{ @@ -41,18 +38,12 @@ use crate::{ pub(crate) async fn run(args: ProverInitArgs, shell: &Shell) -> anyhow::Result<()> { let ecosystem_config = EcosystemConfig::from_file(shell)?; - let default_plonk_key_path = get_default_plonk_compressor_keys_path(&ecosystem_config)?; - let default_fflonk_key_path = get_default_fflonk_compressor_keys_path(&ecosystem_config)?; + let default_compressor_key_path = get_default_compressor_keys_path(&ecosystem_config)?; let chain_config = ecosystem_config .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let args = args.fill_values_with_prompt( - shell, - &default_plonk_key_path, - &default_fflonk_key_path, - &chain_config, - )?; + let args = args.fill_values_with_prompt(shell, &default_compressor_key_path, &chain_config)?; if chain_config.get_general_config().is_err() || chain_config.get_secrets_config().is_err() { copy_configs(shell, &ecosystem_config.link_to_code, &chain_config.configs)?; @@ -66,35 +57,9 @@ pub(crate) async fn run(args: ProverInitArgs, shell: &Shell) -> anyhow::Result<( let public_object_store_config = get_object_store_config(shell, args.public_store)?; if let Some(args) = args.compressor_key_args { - match args.compressor_type { - CompressorType::Fflonk => { - let path = args.clone().fflonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - - download_compressor_key(shell, &mut general_config, CompressorType::Fflonk, &path)?; - } - CompressorType::Plonk => { - let path = args.plonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - - download_compressor_key(shell, &mut general_config, CompressorType::Plonk, &path)?; - } - CompressorType::All => { - let fflonk_path = args.clone().fflonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - let plonk_path = args.clone().plonk_path.context(MSG_SETUP_KEY_PATH_ERROR)?; - - download_compressor_key( - shell, - &mut general_config, - CompressorType::Fflonk, - &fflonk_path, - )?; - download_compressor_key( - shell, - &mut general_config, - CompressorType::Plonk, - &plonk_path, - )?; - } - } + let path = args.path.context(MSG_SETUP_KEY_PATH_ERROR)?; + + download_compressor_key(shell, &mut general_config, &path)?; } if let Some(args) = args.setup_keys { From 2b5fe983acf78f73fb6e90a6a7d041e8aef1c595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Grze=C5=9Bkiewicz?= Date: Thu, 23 Jan 2025 18:52:09 +0100 Subject: [PATCH 31/52] fix(en): better defaults, i.e. the same as used by main node (#3521) Current defaults for EN for gas estimation are different from values used everywhere, this PR addresses that problem --- core/bin/external_node/src/config/mod.rs | 6 +++--- core/bin/external_node/src/config/tests.rs | 4 +++- .../external-node/prepared_configs/mainnet-config.env | 6 +++--- .../prepared_configs/testnet-goerli-config-deprecated.env | 6 +++--- .../prepared_configs/testnet-sepolia-config.env | 6 +++--- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/core/bin/external_node/src/config/mod.rs b/core/bin/external_node/src/config/mod.rs index 235802e1073b..3f1727c619b4 100644 --- a/core/bin/external_node/src/config/mod.rs +++ b/core/bin/external_node/src/config/mod.rs @@ -772,15 +772,15 @@ impl OptionalENConfig { } const fn default_estimate_gas_scale_factor() -> f64 { - 1.2 + 1.3 } const fn default_estimate_gas_acceptable_overestimation() -> u32 { - 1_000 + 5_000 } const fn default_gas_price_scale_factor() -> f64 { - 1.2 + 1.5 } const fn default_max_nonce_ahead() -> u32 { diff --git a/core/bin/external_node/src/config/tests.rs b/core/bin/external_node/src/config/tests.rs index dc74d124b18e..4e23fa78a4f7 100644 --- a/core/bin/external_node/src/config/tests.rs +++ b/core/bin/external_node/src/config/tests.rs @@ -82,7 +82,9 @@ fn parsing_optional_config_from_empty_env() { Duration::from_millis(100) ); assert_eq!(config.max_nonce_ahead, 50); - assert_eq!(config.estimate_gas_scale_factor, 1.2); + assert_eq!(config.estimate_gas_scale_factor, 1.3); + assert_eq!(config.gas_price_scale_factor, 1.5); + assert_eq!(config.estimate_gas_acceptable_overestimation, 5000); assert_eq!(config.vm_concurrency_limit, 2_048); assert_eq!(config.factory_deps_cache_size(), 128 * BYTES_IN_MEGABYTE); assert_eq!(config.latest_values_cache_size(), 128 * BYTES_IN_MEGABYTE); diff --git a/docs/src/guides/external-node/prepared_configs/mainnet-config.env b/docs/src/guides/external-node/prepared_configs/mainnet-config.env index eac24f4ab7ed..536f868fce05 100644 --- a/docs/src/guides/external-node/prepared_configs/mainnet-config.env +++ b/docs/src/guides/external-node/prepared_configs/mainnet-config.env @@ -46,11 +46,11 @@ EN_PUBSUB_POLLING_INTERVAL=200 EN_MAX_NONCE_AHEAD=50 # The multiplier to use when suggesting gas price. Should be higher than one, # otherwise if the L1 prices soar, the suggested gas price won't be sufficient to be included in block. -EN_GAS_PRICE_SCALE_FACTOR=1.2 +EN_GAS_PRICE_SCALE_FACTOR=1.5 # The factor by which to scale the gasLimit -EN_ESTIMATE_GAS_SCALE_FACTOR=1.2 +EN_ESTIMATE_GAS_SCALE_FACTOR=1.3 # The max possible number of gas that `eth_estimateGas` is allowed to overestimate. -EN_ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION=1000 +EN_ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION=5000 # Max possible size of an ABI encoded tx (in bytes). # This shouldn't be larger than the value on the main node. EN_MAX_TX_SIZE=1000000 diff --git a/docs/src/guides/external-node/prepared_configs/testnet-goerli-config-deprecated.env b/docs/src/guides/external-node/prepared_configs/testnet-goerli-config-deprecated.env index eb8b6481d75f..995690ef9b10 100644 --- a/docs/src/guides/external-node/prepared_configs/testnet-goerli-config-deprecated.env +++ b/docs/src/guides/external-node/prepared_configs/testnet-goerli-config-deprecated.env @@ -46,11 +46,11 @@ EN_PUBSUB_POLLING_INTERVAL=200 EN_MAX_NONCE_AHEAD=50 # The multiplier to use when suggesting gas price. Should be higher than one, # otherwise if the L1 prices soar, the suggested gas price won't be sufficient to be included in block. -EN_GAS_PRICE_SCALE_FACTOR=1.2 +EN_GAS_PRICE_SCALE_FACTOR=1.5 # The factor by which to scale the gasLimit -EN_ESTIMATE_GAS_SCALE_FACTOR=1.2 +EN_ESTIMATE_GAS_SCALE_FACTOR=1.3 # The max possible number of gas that `eth_estimateGas` is allowed to overestimate. -EN_ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION=1000 +EN_ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION=5000 # Max possible size of an ABI encoded tx (in bytes). # This shouldn't be larger than the value on the main node. EN_MAX_TX_SIZE=1000000 diff --git a/docs/src/guides/external-node/prepared_configs/testnet-sepolia-config.env b/docs/src/guides/external-node/prepared_configs/testnet-sepolia-config.env index c8f855b4a4a2..cd7c791898e4 100644 --- a/docs/src/guides/external-node/prepared_configs/testnet-sepolia-config.env +++ b/docs/src/guides/external-node/prepared_configs/testnet-sepolia-config.env @@ -46,11 +46,11 @@ EN_PUBSUB_POLLING_INTERVAL=200 EN_MAX_NONCE_AHEAD=50 # The multiplier to use when suggesting gas price. Should be higher than one, # otherwise if the L1 prices soar, the suggested gas price won't be sufficient to be included in block. -EN_GAS_PRICE_SCALE_FACTOR=1.2 +EN_GAS_PRICE_SCALE_FACTOR=1.5 # The factor by which to scale the gasLimit -EN_ESTIMATE_GAS_SCALE_FACTOR=1.2 +EN_ESTIMATE_GAS_SCALE_FACTOR=1.3 # The max possible number of gas that `eth_estimateGas` is allowed to overestimate. -EN_ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION=1000 +EN_ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION=5000 # Max possible size of an ABI encoded tx (in bytes). # This shouldn't be larger than the value on the main node. EN_MAX_TX_SIZE=1000000 From add00a2ad63d4ceed6c6eb5185bf6be65448a84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Grze=C5=9Bkiewicz?= Date: Thu, 23 Jan 2025 18:59:40 +0100 Subject: [PATCH 32/52] feat(integration tests): Flakiness tests on push to main (#3454) Adding repeated runs of integration tests for easier tracking of tests flakiness. A single such check takes ~1h and should cost ~2$, which imo is more than acceptable --- .github/workflows/ci-core-reusable.yml | 27 ++++++++++++++++++++++++++ etc/env/file_based/general.yaml | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-core-reusable.yml b/.github/workflows/ci-core-reusable.yml index f44b1f54dc02..6be2a05e52e2 100644 --- a/.github/workflows/ci-core-reusable.yml +++ b/.github/workflows/ci-core-reusable.yml @@ -462,6 +462,15 @@ jobs: run: | ci_run ./bin/run_on_all_chains.sh "zkstack dev test integration --no-deps --ignore-prerequisites" ${{ env.CHAINS }} ${{ env.INTEGRATION_TESTS_LOGS_DIR }} + - name: Repeat integration tests on push to main to check for flakiness + if: ${{ (steps.condition.outputs.should_run == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main') }} + run: | + for i in {1..10}; do + echo "Iteration $i" + mkdir -p ${{ env.INTEGRATION_TESTS_LOGS_DIR }}/$i + ci_run ./bin/run_on_all_chains.sh "zkstack dev test integration --no-deps --ignore-prerequisites" ${{ env.CHAINS }} ${{ env.INTEGRATION_TESTS_LOGS_DIR }}/$i + done + - name: Init external nodes run: | GATEWAY_RPC_URL="${{ matrix.use_gateway_chain == 'WITH_GATEWAY' && '--gateway-rpc-url=http://localhost:3550' || '' }}" @@ -506,6 +515,15 @@ jobs: run: | ci_run ./bin/run_on_all_chains.sh "zkstack dev test integration --no-deps --ignore-prerequisites --external-node" ${{ env.CHAINS }} ${{ env.INTEGRATION_TESTS_LOGS_DIR }} + - name: Repeat integration tests en on push to main to check for flakiness + if: ${{ (steps.condition.outputs.should_run == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main') }} + run: | + for i in {1..10}; do + echo "Iteration $i" + mkdir -p ${{ env.INTEGRATION_TESTS_LOGS_DIR }}/$i + ci_run ./bin/run_on_all_chains.sh "zkstack dev test integration --no-deps --ignore-prerequisites --external-node" ${{ env.CHAINS }} ${{ env.INTEGRATION_TESTS_LOGS_DIR }}/$i + done + - name: Fee projection tests run: | ci_run killall -INT zksync_server || true @@ -519,6 +537,15 @@ jobs: # Always run the chain-specific fee tests ci_run ./bin/run_on_all_chains.sh "zkstack dev test fees --no-deps --no-kill" ${{ env.CHAINS }} ${{ env.FEES_LOGS_DIR }} + - name: Repeat fee projection tests on push to main to check for flakiness + if: ${{ (steps.condition.outputs.should_run == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main') }} + run: | + for i in {1..10}; do + echo "Iteration $i" + mkdir -p ${{ env.FEES_LOGS_DIR }}/$i + ci_run ./bin/run_on_all_chains.sh "zkstack dev test fees --no-deps --no-kill" ${{ env.CHAINS }} ${{ env.FEES_LOGS_DIR }}/$i + done + - name: Run revert tests run: | ci_run killall -INT zksync_server || true diff --git a/etc/env/file_based/general.yaml b/etc/env/file_based/general.yaml index ece5a1156c5f..76d96fd0e8c4 100644 --- a/etc/env/file_based/general.yaml +++ b/etc/env/file_based/general.yaml @@ -359,7 +359,7 @@ pruning: enabled: true chunk_size: 10 removal_delay_sec: 60 - data_retention_sec: 3600 + data_retention_sec: 604800 commitment_generator: max_parallelism: 10 From 30baf32d484634f69c2bd0f458646237093da909 Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Fri, 24 Jan 2025 08:30:05 +0000 Subject: [PATCH 33/52] chore: temporary return to path cross-workspace (#3523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Temporary return to path cross-workspace dependencies before we publish everything to crates.io. ## 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`. Co-authored-by: perekopskiy <53865202+perekopskiy@users.noreply.github.com> --- prover/Cargo.toml | 34 +++++++++++++++++----------------- zkstack_cli/Cargo.toml | 16 ++++++++-------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 194f6b90a2e7..4e2483cbf0c0 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -81,23 +81,23 @@ shivini = "=0.152.11" boojum-cuda = "=0.152.11" # Core workspace dependencies -zksync_multivm = { version = "=26.1.0-non-semver-compat", path = "../core/lib/multivm" } -zksync_vlog = { version = "=26.1.0-non-semver-compat", path = "../core/lib/vlog" } -zksync_basic_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/basic_types" } -zksync_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/config" } -zksync_dal = { version = "=26.1.0-non-semver-compat", path = "../core/lib/dal" } -zksync_db_connection = { version = "=26.1.0-non-semver-compat", path = "../core/lib/db_connection" } -zksync_env_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/env_config" } -zksync_object_store = { version = "=26.1.0-non-semver-compat", path = "../core/lib/object_store" } -zksync_prover_interface = { version = "=26.1.0-non-semver-compat", path = "../core/lib/prover_interface" } -zksync_queued_job_processor = { version = "=26.1.0-non-semver-compat", path = "../core/lib/queued_job_processor" } -zksync_system_constants = { version = "=26.1.0-non-semver-compat", path = "../core/lib/constants" } -zksync_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/types" } -zksync_utils = { version = "=26.1.0-non-semver-compat", path = "../core/lib/utils" } -zksync_eth_client = { version = "=26.1.0-non-semver-compat", path = "../core/lib/eth_client" } -zksync_contracts = { version = "=26.1.0-non-semver-compat", path = "../core/lib/contracts" } -zksync_core_leftovers = { version = "=26.1.0-non-semver-compat", path = "../core/lib/zksync_core_leftovers" } -zksync_protobuf_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/protobuf_config" } +zksync_multivm = { path = "../core/lib/multivm" } +zksync_vlog = { path = "../core/lib/vlog" } +zksync_basic_types = { path = "../core/lib/basic_types" } +zksync_config = { path = "../core/lib/config" } +zksync_dal = { path = "../core/lib/dal" } +zksync_db_connection = { path = "../core/lib/db_connection" } +zksync_env_config = { path = "../core/lib/env_config" } +zksync_object_store = { path = "../core/lib/object_store" } +zksync_prover_interface = { path = "../core/lib/prover_interface" } +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_eth_client = { path = "../core/lib/eth_client" } +zksync_contracts = { path = "../core/lib/contracts" } +zksync_core_leftovers = { path = "../core/lib/zksync_core_leftovers" } +zksync_protobuf_config = { path = "../core/lib/protobuf_config" } # Prover workspace dependencies zksync_prover_dal = { version = "17.1.1", path = "crates/lib/prover_dal" } diff --git a/zkstack_cli/Cargo.toml b/zkstack_cli/Cargo.toml index 0d726ccc3600..cd5e84fc9eb9 100644 --- a/zkstack_cli/Cargo.toml +++ b/zkstack_cli/Cargo.toml @@ -27,14 +27,14 @@ zkstack_cli_types = { version = "0.1.2", path = "crates/types" } zkstack_cli_git_version_macro = { version = "0.1.2", path = "crates/git_version_macro" } # ZkSync deps -zksync_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/config" } -zksync_protobuf_config = { version = "=26.1.0-non-semver-compat", path = "../core/lib/protobuf_config" } -zksync_basic_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/basic_types" } -zksync_system_constants = { version = "=26.1.0-non-semver-compat", path = "../core/lib/constants" } -zksync_types = { version = "=26.1.0-non-semver-compat", path = "../core/lib/types" } -zksync_web3_decl = { version = "=26.1.0-non-semver-compat", path = "../core/lib/web3_decl" } -zksync_eth_client = { version = "=26.1.0-non-semver-compat", path = "../core/lib/eth_client" } -zksync_contracts = { version = "=26.1.0-non-semver-compat", path = "../core/lib/contracts" } +zksync_config = { path = "../core/lib/config" } +zksync_protobuf_config = { path = "../core/lib/protobuf_config" } +zksync_basic_types = { path = "../core/lib/basic_types" } +zksync_system_constants = { path = "../core/lib/constants" } +zksync_types = { path = "../core/lib/types" } +zksync_web3_decl = { path = "../core/lib/web3_decl" } +zksync_eth_client = { path = "../core/lib/eth_client" } +zksync_contracts = { path = "../core/lib/contracts" } zksync_consensus_roles = "=0.8.0" zksync_consensus_crypto = "=0.8.0" zksync_consensus_utils = "=0.8.0" From 62aea8b4dcd986587de2cf17979e1042307d6b3e Mon Sep 17 00:00:00 2001 From: Artem Fomiuk <88630083+Artemka374@users.noreply.github.com> Date: Fri, 24 Jan 2025 10:44:42 +0200 Subject: [PATCH 34/52] fix: Compressor setup data (#3526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Load compressor setup data from the right dir ## 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`. --- prover/crates/lib/keystore/src/keystore.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/prover/crates/lib/keystore/src/keystore.rs b/prover/crates/lib/keystore/src/keystore.rs index eec7b4c8344e..f69e371cac48 100644 --- a/prover/crates/lib/keystore/src/keystore.rs +++ b/prover/crates/lib/keystore/src/keystore.rs @@ -132,12 +132,12 @@ impl Keystore { ProverServiceDataType::FinalizationHints => self .basedir .join(format!("finalization_hints_{}.bin", name)), - ProverServiceDataType::PlonkSetupData => { - self.basedir.join(format!("plonk_setup_{}_data.bin", name)) - } - ProverServiceDataType::FflonkSetupData => { - self.basedir.join(format!("fflonk_setup_{}_data.bin", name)) - } + ProverServiceDataType::PlonkSetupData => self + .setup_data_path + .join(format!("plonk_setup_{}_data.bin", name)), + ProverServiceDataType::FflonkSetupData => self + .setup_data_path + .join(format!("fflonk_setup_{}_data.bin", name)), ProverServiceDataType::SnarkVerificationKey => { self.basedir.join(format!("verification_{}_key.json", name)) } From fc06147edfb66273a4978b972a83cbe171acbaf0 Mon Sep 17 00:00:00 2001 From: zksync-era-bot <147085853+zksync-era-bot@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:02:51 +0400 Subject: [PATCH 35/52] chore(main): release core 26.2.0 (#3528) :robot: I have created a release *beep* *boop* --- ## [26.2.0](https://github.com/matter-labs/zksync-era/compare/core-v26.1.0...core-v26.2.0) (2025-01-24) ### Features * Compressor optimizations ([#3476](https://github.com/matter-labs/zksync-era/issues/3476)) ([3e931be](https://github.com/matter-labs/zksync-era/commit/3e931be6bddaacbd7d029c537db03a3c191fdc21)) ### Bug Fixes * **en:** better defaults, i.e. the same as used by main node ([#3521](https://github.com/matter-labs/zksync-era/issues/3521)) ([2b5fe98](https://github.com/matter-labs/zksync-era/commit/2b5fe983acf78f73fb6e90a6a7d041e8aef1c595)) * **en:** Fix race condition in EN storage initialization ([#3515](https://github.com/matter-labs/zksync-era/issues/3515)) ([c916797](https://github.com/matter-labs/zksync-era/commit/c916797d49d636c9e642264786d4124ebd338ec3)) * JSON proof serialization ([#3514](https://github.com/matter-labs/zksync-era/issues/3514)) ([516e521](https://github.com/matter-labs/zksync-era/commit/516e5210ed70b25a15a68a58c8065331aab542e0)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: zksync-era-bot Co-authored-by: Anton Baliasnikov --- .github/release-please/manifest.json | 2 +- core/CHANGELOG.md | 14 +++ core/Cargo.lock | 156 +++++++++++++-------------- core/Cargo.toml | 130 +++++++++++----------- prover/Cargo.lock | 46 ++++---- zkstack_cli/Cargo.lock | 26 ++--- 6 files changed, 194 insertions(+), 180 deletions(-) diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index 0ba598bb0516..4185bd5c59a5 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,5 +1,5 @@ { - "core": "26.1.0", + "core": "26.2.0", "prover": "17.1.1", "zkstack_cli": "0.1.2" } diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 256c83058ee2..0354b180b0bb 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [26.2.0](https://github.com/matter-labs/zksync-era/compare/core-v26.1.0...core-v26.2.0) (2025-01-24) + + +### Features + +* Compressor optimizations ([#3476](https://github.com/matter-labs/zksync-era/issues/3476)) ([3e931be](https://github.com/matter-labs/zksync-era/commit/3e931be6bddaacbd7d029c537db03a3c191fdc21)) + + +### Bug Fixes + +* **en:** better defaults, i.e. the same as used by main node ([#3521](https://github.com/matter-labs/zksync-era/issues/3521)) ([2b5fe98](https://github.com/matter-labs/zksync-era/commit/2b5fe983acf78f73fb6e90a6a7d041e8aef1c595)) +* **en:** Fix race condition in EN storage initialization ([#3515](https://github.com/matter-labs/zksync-era/issues/3515)) ([c916797](https://github.com/matter-labs/zksync-era/commit/c916797d49d636c9e642264786d4124ebd338ec3)) +* JSON proof serialization ([#3514](https://github.com/matter-labs/zksync-era/issues/3514)) ([516e521](https://github.com/matter-labs/zksync-era/commit/516e5210ed70b25a15a68a58c8065331aab542e0)) + ## [26.1.0](https://github.com/matter-labs/zksync-era/compare/core-v26.0.0...core-v26.1.0) (2025-01-21) diff --git a/core/Cargo.lock b/core/Cargo.lock index 1b251bec741d..7c59753d45bc 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1233,7 +1233,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "block_reverter" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -2233,7 +2233,7 @@ dependencies = [ [[package]] name = "custom_genesis_export" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -3485,7 +3485,7 @@ dependencies = [ [[package]] name = "genesis_generator" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -5156,7 +5156,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "loadnext" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -5343,7 +5343,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "merkle_tree_consistency_checker" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -7984,7 +7984,7 @@ dependencies = [ [[package]] name = "selector_generator" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -8644,7 +8644,7 @@ dependencies = [ [[package]] name = "snapshots_creator" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "futures 0.3.31", @@ -9393,7 +9393,7 @@ dependencies = [ [[package]] name = "system-constants-generator" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "codegen", "once_cell", @@ -10373,7 +10373,7 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "verified_sources_fetcher" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "serde_json", @@ -10430,7 +10430,7 @@ dependencies = [ [[package]] name = "vm-benchmark" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "assert_matches", "criterion", @@ -11293,7 +11293,7 @@ dependencies = [ [[package]] name = "zksync_base_token_adjuster" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11315,7 +11315,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -11360,7 +11360,7 @@ dependencies = [ [[package]] name = "zksync_block_reverter" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11384,7 +11384,7 @@ dependencies = [ [[package]] name = "zksync_circuit_breaker" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11398,7 +11398,7 @@ dependencies = [ [[package]] name = "zksync_commitment_generator" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "circuit_encodings", @@ -11446,7 +11446,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -11619,7 +11619,7 @@ dependencies = [ [[package]] name = "zksync_consistency_checker" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11644,7 +11644,7 @@ dependencies = [ [[package]] name = "zksync_contract_verification_server" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "axum 0.7.9", @@ -11663,7 +11663,7 @@ dependencies = [ [[package]] name = "zksync_contract_verifier" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -11680,7 +11680,7 @@ dependencies = [ [[package]] name = "zksync_contract_verifier_lib" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11712,7 +11712,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "bincode", "envy", @@ -11726,7 +11726,7 @@ dependencies = [ [[package]] name = "zksync_core_leftovers" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "ctrlc", @@ -11740,7 +11740,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "blake2 0.10.6", @@ -11768,7 +11768,7 @@ dependencies = [ [[package]] name = "zksync_da_client" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11777,7 +11777,7 @@ dependencies = [ [[package]] name = "zksync_da_clients" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11821,7 +11821,7 @@ dependencies = [ [[package]] name = "zksync_da_dispatcher" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -11838,7 +11838,7 @@ dependencies = [ [[package]] name = "zksync_dal" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "bigdecimal", @@ -11874,7 +11874,7 @@ dependencies = [ [[package]] name = "zksync_db_connection" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11892,7 +11892,7 @@ dependencies = [ [[package]] name = "zksync_env_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "envy", @@ -11904,7 +11904,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "assert_matches", "async-trait", @@ -11926,7 +11926,7 @@ dependencies = [ [[package]] name = "zksync_eth_sender" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11956,7 +11956,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -11967,7 +11967,7 @@ dependencies = [ [[package]] name = "zksync_eth_watch" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-recursion", @@ -11994,7 +11994,7 @@ dependencies = [ [[package]] name = "zksync_external_node" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12048,7 +12048,7 @@ dependencies = [ [[package]] name = "zksync_external_price_api" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12069,7 +12069,7 @@ dependencies = [ [[package]] name = "zksync_external_proof_integration_api" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12115,7 +12115,7 @@ dependencies = [ [[package]] name = "zksync_health_check" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "assert_matches", "async-trait", @@ -12130,7 +12130,7 @@ dependencies = [ [[package]] name = "zksync_house_keeper" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12162,7 +12162,7 @@ dependencies = [ [[package]] name = "zksync_l1_contract_interface" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "circuit_definitions", @@ -12183,7 +12183,7 @@ dependencies = [ [[package]] name = "zksync_logs_bloom_backfill" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "tokio", @@ -12195,7 +12195,7 @@ dependencies = [ [[package]] name = "zksync_mempool" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "tracing", "zksync_types", @@ -12203,7 +12203,7 @@ dependencies = [ [[package]] name = "zksync_merkle_tree" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12232,7 +12232,7 @@ dependencies = [ [[package]] name = "zksync_metadata_calculator" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12266,7 +12266,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "criterion", "once_cell", @@ -12276,7 +12276,7 @@ dependencies = [ [[package]] name = "zksync_multivm" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12308,7 +12308,7 @@ dependencies = [ [[package]] name = "zksync_node_api_server" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12362,7 +12362,7 @@ dependencies = [ [[package]] name = "zksync_node_consensus" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12406,7 +12406,7 @@ dependencies = [ [[package]] name = "zksync_node_db_pruner" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12428,7 +12428,7 @@ dependencies = [ [[package]] name = "zksync_node_fee_model" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12447,7 +12447,7 @@ dependencies = [ [[package]] name = "zksync_node_framework" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12510,7 +12510,7 @@ dependencies = [ [[package]] name = "zksync_node_framework_derive" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "proc-macro2 1.0.92", "quote 1.0.37", @@ -12519,7 +12519,7 @@ dependencies = [ [[package]] name = "zksync_node_genesis" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -12540,7 +12540,7 @@ dependencies = [ [[package]] name = "zksync_node_storage_init" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12562,7 +12562,7 @@ dependencies = [ [[package]] name = "zksync_node_sync" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12597,7 +12597,7 @@ dependencies = [ [[package]] name = "zksync_node_test_utils" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "zksync_contracts", "zksync_dal", @@ -12609,7 +12609,7 @@ dependencies = [ [[package]] name = "zksync_object_store" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12647,7 +12647,7 @@ dependencies = [ [[package]] name = "zksync_proof_data_handler" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "axum 0.7.9", @@ -12709,7 +12709,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -12729,7 +12729,7 @@ dependencies = [ [[package]] name = "zksync_prover_interface" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "bincode", "chrono", @@ -12749,7 +12749,7 @@ dependencies = [ [[package]] name = "zksync_queued_job_processor" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12761,7 +12761,7 @@ dependencies = [ [[package]] name = "zksync_reorg_detector" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12782,7 +12782,7 @@ dependencies = [ [[package]] name = "zksync_server" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -12812,7 +12812,7 @@ dependencies = [ [[package]] name = "zksync_shared_metrics" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "rustc_version 0.4.1", "serde", @@ -12824,7 +12824,7 @@ dependencies = [ [[package]] name = "zksync_snapshots_applier" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12863,7 +12863,7 @@ dependencies = [ [[package]] name = "zksync_state" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12888,7 +12888,7 @@ dependencies = [ [[package]] name = "zksync_state_keeper" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12928,7 +12928,7 @@ dependencies = [ [[package]] name = "zksync_storage" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "num_cpus", "once_cell", @@ -12941,7 +12941,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -12949,7 +12949,7 @@ dependencies = [ [[package]] name = "zksync_tee_prover" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12974,7 +12974,7 @@ dependencies = [ [[package]] name = "zksync_tee_verifier" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -12992,7 +12992,7 @@ dependencies = [ [[package]] name = "zksync_test_contracts" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "ethabi", "foundry-compilers", @@ -13008,7 +13008,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13043,7 +13043,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13059,7 +13059,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -13104,7 +13104,7 @@ dependencies = [ [[package]] name = "zksync_vm_executor" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13122,7 +13122,7 @@ dependencies = [ [[package]] name = "zksync_vm_interface" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13140,7 +13140,7 @@ dependencies = [ [[package]] name = "zksync_vm_runner" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13174,7 +13174,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "assert_matches", diff --git a/core/Cargo.toml b/core/Cargo.toml index 80e9ac035283..99665b2bab28 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -91,7 +91,7 @@ inherits = "release" debug = true [workspace.package] -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -258,70 +258,70 @@ zksync_protobuf = "=0.8.0" zksync_protobuf_build = "=0.8.0" # "Local" dependencies -zksync_multivm = { version = "=26.1.0-non-semver-compat", path = "lib/multivm" } -zksync_vlog = { version = "=26.1.0-non-semver-compat", path = "lib/vlog" } -zksync_vm_interface = { version = "=26.1.0-non-semver-compat", path = "lib/vm_interface" } -zksync_vm_executor = { version = "=26.1.0-non-semver-compat", path = "lib/vm_executor" } -zksync_basic_types = { version = "=26.1.0-non-semver-compat", path = "lib/basic_types" } -zksync_circuit_breaker = { version = "=26.1.0-non-semver-compat", path = "lib/circuit_breaker" } -zksync_config = { version = "=26.1.0-non-semver-compat", path = "lib/config" } -zksync_contract_verifier_lib = { version = "=26.1.0-non-semver-compat", path = "lib/contract_verifier" } -zksync_contracts = { version = "=26.1.0-non-semver-compat", path = "lib/contracts" } -zksync_core_leftovers = { version = "=26.1.0-non-semver-compat", path = "lib/zksync_core_leftovers" } -zksync_dal = { version = "=26.1.0-non-semver-compat", path = "lib/dal" } -zksync_db_connection = { version = "=26.1.0-non-semver-compat", path = "lib/db_connection" } -zksync_env_config = { version = "=26.1.0-non-semver-compat", path = "lib/env_config" } -zksync_eth_client = { version = "=26.1.0-non-semver-compat", path = "lib/eth_client" } -zksync_da_client = { version = "=26.1.0-non-semver-compat", path = "lib/da_client" } -zksync_eth_signer = { version = "=26.1.0-non-semver-compat", path = "lib/eth_signer" } -zksync_health_check = { version = "=26.1.0-non-semver-compat", path = "lib/health_check" } -zksync_l1_contract_interface = { version = "=26.1.0-non-semver-compat", path = "lib/l1_contract_interface" } -zksync_mempool = { version = "=26.1.0-non-semver-compat", path = "lib/mempool" } -zksync_merkle_tree = { version = "=26.1.0-non-semver-compat", path = "lib/merkle_tree" } +zksync_multivm = { version = "26.2.0-non-semver-compat", path = "lib/multivm" } +zksync_vlog = { version = "26.2.0-non-semver-compat", path = "lib/vlog" } +zksync_vm_interface = { version = "26.2.0-non-semver-compat", path = "lib/vm_interface" } +zksync_vm_executor = { version = "26.2.0-non-semver-compat", path = "lib/vm_executor" } +zksync_basic_types = { version = "26.2.0-non-semver-compat", path = "lib/basic_types" } +zksync_circuit_breaker = { version = "26.2.0-non-semver-compat", path = "lib/circuit_breaker" } +zksync_config = { version = "26.2.0-non-semver-compat", path = "lib/config" } +zksync_contract_verifier_lib = { version = "26.2.0-non-semver-compat", path = "lib/contract_verifier" } +zksync_contracts = { version = "26.2.0-non-semver-compat", path = "lib/contracts" } +zksync_core_leftovers = { version = "26.2.0-non-semver-compat", path = "lib/zksync_core_leftovers" } +zksync_dal = { version = "26.2.0-non-semver-compat", path = "lib/dal" } +zksync_db_connection = { version = "26.2.0-non-semver-compat", path = "lib/db_connection" } +zksync_env_config = { version = "26.2.0-non-semver-compat", path = "lib/env_config" } +zksync_eth_client = { version = "26.2.0-non-semver-compat", path = "lib/eth_client" } +zksync_da_client = { version = "26.2.0-non-semver-compat", path = "lib/da_client" } +zksync_eth_signer = { version = "26.2.0-non-semver-compat", path = "lib/eth_signer" } +zksync_health_check = { version = "26.2.0-non-semver-compat", path = "lib/health_check" } +zksync_l1_contract_interface = { version = "26.2.0-non-semver-compat", path = "lib/l1_contract_interface" } +zksync_mempool = { version = "26.2.0-non-semver-compat", path = "lib/mempool" } +zksync_merkle_tree = { version = "26.2.0-non-semver-compat", path = "lib/merkle_tree" } zksync_bin_metadata = { version = "=26.1.0-non-semver-compat", path = "lib/bin_metadata" } -zksync_mini_merkle_tree = { version = "=26.1.0-non-semver-compat", path = "lib/mini_merkle_tree" } -zksync_object_store = { version = "=26.1.0-non-semver-compat", path = "lib/object_store" } -zksync_protobuf_config = { version = "=26.1.0-non-semver-compat", path = "lib/protobuf_config" } -zksync_prover_interface = { version = "=26.1.0-non-semver-compat", path = "lib/prover_interface" } -zksync_queued_job_processor = { version = "=26.1.0-non-semver-compat", path = "lib/queued_job_processor" } -zksync_snapshots_applier = { version = "=26.1.0-non-semver-compat", path = "lib/snapshots_applier" } -zksync_state = { version = "=26.1.0-non-semver-compat", path = "lib/state" } -zksync_storage = { version = "=26.1.0-non-semver-compat", path = "lib/storage" } -zksync_system_constants = { version = "=26.1.0-non-semver-compat", path = "lib/constants" } -zksync_tee_verifier = { version = "=26.1.0-non-semver-compat", path = "lib/tee_verifier" } -zksync_test_contracts = { version = "=26.1.0-non-semver-compat", path = "lib/test_contracts" } -zksync_types = { version = "=26.1.0-non-semver-compat", path = "lib/types" } -zksync_utils = { version = "=26.1.0-non-semver-compat", path = "lib/utils" } -zksync_web3_decl = { version = "=26.1.0-non-semver-compat", path = "lib/web3_decl" } -zksync_crypto_primitives = { version = "=26.1.0-non-semver-compat", path = "lib/crypto_primitives" } -zksync_external_price_api = { version = "=26.1.0-non-semver-compat", path = "lib/external_price_api" } +zksync_mini_merkle_tree = { version = "26.2.0-non-semver-compat", path = "lib/mini_merkle_tree" } +zksync_object_store = { version = "26.2.0-non-semver-compat", path = "lib/object_store" } +zksync_protobuf_config = { version = "26.2.0-non-semver-compat", path = "lib/protobuf_config" } +zksync_prover_interface = { version = "26.2.0-non-semver-compat", path = "lib/prover_interface" } +zksync_queued_job_processor = { version = "26.2.0-non-semver-compat", path = "lib/queued_job_processor" } +zksync_snapshots_applier = { version = "26.2.0-non-semver-compat", path = "lib/snapshots_applier" } +zksync_state = { version = "26.2.0-non-semver-compat", path = "lib/state" } +zksync_storage = { version = "26.2.0-non-semver-compat", path = "lib/storage" } +zksync_system_constants = { version = "26.2.0-non-semver-compat", path = "lib/constants" } +zksync_tee_verifier = { version = "26.2.0-non-semver-compat", path = "lib/tee_verifier" } +zksync_test_contracts = { version = "26.2.0-non-semver-compat", path = "lib/test_contracts" } +zksync_types = { version = "26.2.0-non-semver-compat", path = "lib/types" } +zksync_utils = { version = "26.2.0-non-semver-compat", path = "lib/utils" } +zksync_web3_decl = { version = "26.2.0-non-semver-compat", path = "lib/web3_decl" } +zksync_crypto_primitives = { version = "26.2.0-non-semver-compat", path = "lib/crypto_primitives" } +zksync_external_price_api = { version = "26.2.0-non-semver-compat", path = "lib/external_price_api" } # Framework and components -zksync_node_framework = { version = "=26.1.0-non-semver-compat", path = "node/node_framework" } -zksync_node_framework_derive = { version = "=26.1.0-non-semver-compat", path = "lib/node_framework_derive" } -zksync_eth_watch = { version = "=26.1.0-non-semver-compat", path = "node/eth_watch" } -zksync_shared_metrics = { version = "=26.1.0-non-semver-compat", path = "node/shared_metrics" } -zksync_proof_data_handler = { version = "=26.1.0-non-semver-compat", path = "node/proof_data_handler" } -zksync_block_reverter = { version = "=26.1.0-non-semver-compat", path = "node/block_reverter" } -zksync_commitment_generator = { version = "=26.1.0-non-semver-compat", path = "node/commitment_generator" } -zksync_house_keeper = { version = "=26.1.0-non-semver-compat", path = "node/house_keeper" } -zksync_node_genesis = { version = "=26.1.0-non-semver-compat", path = "node/genesis" } -zksync_da_dispatcher = { version = "=26.1.0-non-semver-compat", path = "node/da_dispatcher" } -zksync_da_clients = { version = "=26.1.0-non-semver-compat", path = "node/da_clients" } -zksync_eth_sender = { version = "=26.1.0-non-semver-compat", path = "node/eth_sender" } -zksync_node_db_pruner = { version = "=26.1.0-non-semver-compat", path = "node/db_pruner" } -zksync_node_fee_model = { version = "=26.1.0-non-semver-compat", path = "node/fee_model" } -zksync_vm_runner = { version = "=26.1.0-non-semver-compat", path = "node/vm_runner" } -zksync_external_proof_integration_api = { version = "=26.1.0-non-semver-compat", path = "node/external_proof_integration_api" } -zksync_node_test_utils = { version = "=26.1.0-non-semver-compat", path = "node/test_utils" } -zksync_state_keeper = { version = "=26.1.0-non-semver-compat", path = "node/state_keeper" } -zksync_reorg_detector = { version = "=26.1.0-non-semver-compat", path = "node/reorg_detector" } -zksync_consistency_checker = { version = "=26.1.0-non-semver-compat", path = "node/consistency_checker" } -zksync_metadata_calculator = { version = "=26.1.0-non-semver-compat", path = "node/metadata_calculator" } -zksync_node_sync = { version = "=26.1.0-non-semver-compat", path = "node/node_sync" } -zksync_node_storage_init = { version = "=26.1.0-non-semver-compat", path = "node/node_storage_init" } -zksync_node_consensus = { version = "=26.1.0-non-semver-compat", path = "node/consensus" } -zksync_contract_verification_server = { version = "=26.1.0-non-semver-compat", path = "node/contract_verification_server" } -zksync_node_api_server = { version = "=26.1.0-non-semver-compat", path = "node/api_server" } -zksync_base_token_adjuster = { version = "=26.1.0-non-semver-compat", path = "node/base_token_adjuster" } -zksync_logs_bloom_backfill = { version = "=26.1.0-non-semver-compat", path = "node/logs_bloom_backfill" } +zksync_node_framework = { version = "26.2.0-non-semver-compat", path = "node/node_framework" } +zksync_node_framework_derive = { version = "26.2.0-non-semver-compat", path = "lib/node_framework_derive" } +zksync_eth_watch = { version = "26.2.0-non-semver-compat", path = "node/eth_watch" } +zksync_shared_metrics = { version = "26.2.0-non-semver-compat", path = "node/shared_metrics" } +zksync_proof_data_handler = { version = "26.2.0-non-semver-compat", path = "node/proof_data_handler" } +zksync_block_reverter = { version = "26.2.0-non-semver-compat", path = "node/block_reverter" } +zksync_commitment_generator = { version = "26.2.0-non-semver-compat", path = "node/commitment_generator" } +zksync_house_keeper = { version = "26.2.0-non-semver-compat", path = "node/house_keeper" } +zksync_node_genesis = { version = "26.2.0-non-semver-compat", path = "node/genesis" } +zksync_da_dispatcher = { version = "26.2.0-non-semver-compat", path = "node/da_dispatcher" } +zksync_da_clients = { version = "26.2.0-non-semver-compat", path = "node/da_clients" } +zksync_eth_sender = { version = "26.2.0-non-semver-compat", path = "node/eth_sender" } +zksync_node_db_pruner = { version = "26.2.0-non-semver-compat", path = "node/db_pruner" } +zksync_node_fee_model = { version = "26.2.0-non-semver-compat", path = "node/fee_model" } +zksync_vm_runner = { version = "26.2.0-non-semver-compat", path = "node/vm_runner" } +zksync_external_proof_integration_api = { version = "26.2.0-non-semver-compat", path = "node/external_proof_integration_api" } +zksync_node_test_utils = { version = "26.2.0-non-semver-compat", path = "node/test_utils" } +zksync_state_keeper = { version = "26.2.0-non-semver-compat", path = "node/state_keeper" } +zksync_reorg_detector = { version = "26.2.0-non-semver-compat", path = "node/reorg_detector" } +zksync_consistency_checker = { version = "26.2.0-non-semver-compat", path = "node/consistency_checker" } +zksync_metadata_calculator = { version = "26.2.0-non-semver-compat", path = "node/metadata_calculator" } +zksync_node_sync = { version = "26.2.0-non-semver-compat", path = "node/node_sync" } +zksync_node_storage_init = { version = "26.2.0-non-semver-compat", path = "node/node_storage_init" } +zksync_node_consensus = { version = "26.2.0-non-semver-compat", path = "node/consensus" } +zksync_contract_verification_server = { version = "26.2.0-non-semver-compat", path = "node/contract_verification_server" } +zksync_node_api_server = { version = "26.2.0-non-semver-compat", path = "node/api_server" } +zksync_base_token_adjuster = { version = "26.2.0-non-semver-compat", path = "node/base_token_adjuster" } +zksync_logs_bloom_backfill = { version = "26.2.0-non-semver-compat", path = "node/logs_bloom_backfill" } diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 19223f360857..77521740ffd9 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -8070,7 +8070,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -8183,7 +8183,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -8274,7 +8274,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "envy", "hex", @@ -8287,7 +8287,7 @@ dependencies = [ [[package]] name = "zksync_core_leftovers" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "ctrlc", @@ -8301,7 +8301,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "blake2 0.10.6", @@ -8329,7 +8329,7 @@ dependencies = [ [[package]] name = "zksync_dal" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "bigdecimal", @@ -8364,7 +8364,7 @@ dependencies = [ [[package]] name = "zksync_db_connection" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -8380,7 +8380,7 @@ dependencies = [ [[package]] name = "zksync_env_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "envy", @@ -8391,7 +8391,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "async-trait", "jsonrpsee", @@ -8408,7 +8408,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -8464,7 +8464,7 @@ dependencies = [ [[package]] name = "zksync_l1_contract_interface" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "circuit_definitions", @@ -8481,7 +8481,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -8490,7 +8490,7 @@ dependencies = [ [[package]] name = "zksync_multivm" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "circuit_sequencer_api", @@ -8516,7 +8516,7 @@ dependencies = [ [[package]] name = "zksync_object_store" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8627,7 +8627,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -8782,7 +8782,7 @@ dependencies = [ [[package]] name = "zksync_prover_interface" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "chrono", "circuit_definitions", @@ -8864,7 +8864,7 @@ dependencies = [ [[package]] name = "zksync_queued_job_processor" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8893,7 +8893,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -8901,7 +8901,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8933,7 +8933,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "futures 0.3.31", @@ -8972,7 +8972,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -9017,7 +9017,7 @@ dependencies = [ [[package]] name = "zksync_vm_interface" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -9033,7 +9033,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index bbdb50159076..d3691b9c6e05 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -7165,7 +7165,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -7205,7 +7205,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "rand", @@ -7274,7 +7274,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "envy", "hex", @@ -7287,7 +7287,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "blake2", @@ -7303,7 +7303,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "async-trait", "jsonrpsee", @@ -7320,7 +7320,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -7331,7 +7331,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -7378,7 +7378,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -7398,7 +7398,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -7406,7 +7406,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -7438,7 +7438,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "futures", @@ -7453,7 +7453,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -7478,7 +7478,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "26.1.0-non-semver-compat" +version = "26.2.0-non-semver-compat" dependencies = [ "anyhow", "async-trait", From 092b1990a61202b6a9fb264a74bdd8cf593f3a44 Mon Sep 17 00:00:00 2001 From: Tahsin Tunan Date: Mon, 27 Jan 2025 14:44:01 +0600 Subject: [PATCH 36/52] chore: make some prover modules public (#3530) --- prover/crates/lib/circuit_prover_service/src/lib.rs | 6 +++--- .../crates/lib/circuit_prover_service/src/types/circuit.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prover/crates/lib/circuit_prover_service/src/lib.rs b/prover/crates/lib/circuit_prover_service/src/lib.rs index 19e7bb1f41ee..59132e17c1bc 100644 --- a/prover/crates/lib/circuit_prover_service/src/lib.rs +++ b/prover/crates/lib/circuit_prover_service/src/lib.rs @@ -2,8 +2,8 @@ // Crypto code uses generic const exprs, allocator_api is needed to use global allocators #![feature(generic_const_exprs, allocator_api)] -mod gpu_circuit_prover; +pub mod gpu_circuit_prover; pub mod job_runner; mod metrics; -mod types; -mod witness_vector_generator; +pub mod types; +pub mod witness_vector_generator; diff --git a/prover/crates/lib/circuit_prover_service/src/types/circuit.rs b/prover/crates/lib/circuit_prover_service/src/types/circuit.rs index 264daba63b7d..7185c9abbcd2 100644 --- a/prover/crates/lib/circuit_prover_service/src/types/circuit.rs +++ b/prover/crates/lib/circuit_prover_service/src/types/circuit.rs @@ -134,7 +134,7 @@ impl Circuit { /// Synthesize vector for a given circuit. /// Expects finalization hints to match circuit. - pub(crate) fn synthesize_vector( + pub fn synthesize_vector( &self, finalization_hints: Arc, ) -> anyhow::Result> { From 32a068d1878096403f0fad62326616edc1d88e32 Mon Sep 17 00:00:00 2001 From: zksync-era-bot <147085853+zksync-era-bot@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:25:20 +0400 Subject: [PATCH 37/52] chore(main): release prover 18.0.0 (#3338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :robot: I have created a release *beep* *boop* --- ## [18.0.0](https://github.com/matter-labs/zksync-era/compare/prover-v17.1.1...prover-v18.0.0) (2025-01-27) ### ⚠ BREAKING CHANGES * **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) ### Features * Compressor optimizations ([#3476](https://github.com/matter-labs/zksync-era/issues/3476)) ([3e931be](https://github.com/matter-labs/zksync-era/commit/3e931be6bddaacbd7d029c537db03a3c191fdc21)) * **consensus:** Added view_timeout to consensus config ([#3383](https://github.com/matter-labs/zksync-era/issues/3383)) ([fc02a8f](https://github.com/matter-labs/zksync-era/commit/fc02a8f1c9f0bffb438fb27769d6dced3ce14cd9)) * **consensus:** Update consensus dependencies ([#3339](https://github.com/matter-labs/zksync-era/issues/3339)) ([aa9575f](https://github.com/matter-labs/zksync-era/commit/aa9575fccbbc941f416d597256442afa974efd0a)) * **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) ([f06cb79](https://github.com/matter-labs/zksync-era/commit/f06cb79883bf320f50089099e0abeb95eaace470)) * **eth-watch:** Change protocol upgrade schema ([#3435](https://github.com/matter-labs/zksync-era/issues/3435)) ([2c778fd](https://github.com/matter-labs/zksync-era/commit/2c778fdd3fcd1e774bcb945f14a640ccf4227a2f)) * FFLONK support for compressor ([#3359](https://github.com/matter-labs/zksync-era/issues/3359)) ([1a297be](https://github.com/matter-labs/zksync-era/commit/1a297bedd226c56fc2ba02dc54d79129a271a1eb)) * Support stable compiler for VM (and some other crates) ([#3248](https://github.com/matter-labs/zksync-era/issues/3248)) ([cbee99d](https://github.com/matter-labs/zksync-era/commit/cbee99d8661b38aa6b49784c3934b8070a743fb4)) ### Bug Fixes * added missing quote in prover query ([#3347](https://github.com/matter-labs/zksync-era/issues/3347)) ([668ca51](https://github.com/matter-labs/zksync-era/commit/668ca51f5d52646e64b19b973acec05daa1c6f09)) * Compressor setup data ([#3526](https://github.com/matter-labs/zksync-era/issues/3526)) ([62aea8b](https://github.com/matter-labs/zksync-era/commit/62aea8b4dcd986587de2cf17979e1042307d6b3e)) * **prover:** Create reqwest client only once ([#3324](https://github.com/matter-labs/zksync-era/issues/3324)) ([40f8123](https://github.com/matter-labs/zksync-era/commit/40f8123a67970efbba3519f7954f807958a76cff)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: zksync-era-bot --- .github/release-please/manifest.json | 2 +- prover/CHANGELOG.md | 24 ++++++++++++++++++++ prover/Cargo.lock | 34 ++++++++++++++-------------- prover/Cargo.toml | 18 +++++++-------- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index 4185bd5c59a5..2ebd4f487587 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,5 +1,5 @@ { "core": "26.2.0", - "prover": "17.1.1", + "prover": "18.0.0", "zkstack_cli": "0.1.2" } diff --git a/prover/CHANGELOG.md b/prover/CHANGELOG.md index e99c20193eb5..68a02884328b 100644 --- a/prover/CHANGELOG.md +++ b/prover/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [18.0.0](https://github.com/matter-labs/zksync-era/compare/prover-v17.1.1...prover-v18.0.0) (2025-01-27) + + +### ⚠ BREAKING CHANGES + +* **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) + +### Features + +* Compressor optimizations ([#3476](https://github.com/matter-labs/zksync-era/issues/3476)) ([3e931be](https://github.com/matter-labs/zksync-era/commit/3e931be6bddaacbd7d029c537db03a3c191fdc21)) +* **consensus:** Added view_timeout to consensus config ([#3383](https://github.com/matter-labs/zksync-era/issues/3383)) ([fc02a8f](https://github.com/matter-labs/zksync-era/commit/fc02a8f1c9f0bffb438fb27769d6dced3ce14cd9)) +* **consensus:** Update consensus dependencies ([#3339](https://github.com/matter-labs/zksync-era/issues/3339)) ([aa9575f](https://github.com/matter-labs/zksync-era/commit/aa9575fccbbc941f416d597256442afa974efd0a)) +* **contracts:** gateway integration ([#1934](https://github.com/matter-labs/zksync-era/issues/1934)) ([f06cb79](https://github.com/matter-labs/zksync-era/commit/f06cb79883bf320f50089099e0abeb95eaace470)) +* **eth-watch:** Change protocol upgrade schema ([#3435](https://github.com/matter-labs/zksync-era/issues/3435)) ([2c778fd](https://github.com/matter-labs/zksync-era/commit/2c778fdd3fcd1e774bcb945f14a640ccf4227a2f)) +* FFLONK support for compressor ([#3359](https://github.com/matter-labs/zksync-era/issues/3359)) ([1a297be](https://github.com/matter-labs/zksync-era/commit/1a297bedd226c56fc2ba02dc54d79129a271a1eb)) +* Support stable compiler for VM (and some other crates) ([#3248](https://github.com/matter-labs/zksync-era/issues/3248)) ([cbee99d](https://github.com/matter-labs/zksync-era/commit/cbee99d8661b38aa6b49784c3934b8070a743fb4)) + + +### Bug Fixes + +* added missing quote in prover query ([#3347](https://github.com/matter-labs/zksync-era/issues/3347)) ([668ca51](https://github.com/matter-labs/zksync-era/commit/668ca51f5d52646e64b19b973acec05daa1c6f09)) +* Compressor setup data ([#3526](https://github.com/matter-labs/zksync-era/issues/3526)) ([62aea8b](https://github.com/matter-labs/zksync-era/commit/62aea8b4dcd986587de2cf17979e1042307d6b3e)) +* **prover:** Create reqwest client only once ([#3324](https://github.com/matter-labs/zksync-era/issues/3324)) ([40f8123](https://github.com/matter-labs/zksync-era/commit/40f8123a67970efbba3519f7954f807958a76cff)) + ## [17.1.1](https://github.com/matter-labs/zksync-era/compare/prover-v17.1.0...prover-v17.1.1) (2024-11-26) diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 77521740ffd9..bc33f8d54385 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -4706,7 +4706,7 @@ dependencies = [ [[package]] name = "prover_cli" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "assert_cmd", @@ -4739,7 +4739,7 @@ dependencies = [ [[package]] name = "prover_version" -version = "17.1.1" +version = "18.0.0" dependencies = [ "zksync_prover_fri_types", ] @@ -8114,7 +8114,7 @@ dependencies = [ [[package]] name = "zksync_circuit_prover" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8144,7 +8144,7 @@ dependencies = [ [[package]] name = "zksync_circuit_prover_service" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8552,7 +8552,7 @@ dependencies = [ [[package]] name = "zksync_proof_fri_compressor" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "zksync_prover_autoscaler" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8684,7 +8684,7 @@ dependencies = [ [[package]] name = "zksync_prover_dal" -version = "17.1.1" +version = "18.0.0" dependencies = [ "sqlx", "strum", @@ -8694,7 +8694,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8728,7 +8728,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri_gateway" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8754,7 +8754,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri_types" -version = "17.1.1" +version = "18.0.0" dependencies = [ "circuit_definitions", "serde", @@ -8764,7 +8764,7 @@ dependencies = [ [[package]] name = "zksync_prover_fri_utils" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "regex", @@ -8799,7 +8799,7 @@ dependencies = [ [[package]] name = "zksync_prover_job_monitor" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8821,7 +8821,7 @@ dependencies = [ [[package]] name = "zksync_prover_job_processor" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -8836,7 +8836,7 @@ dependencies = [ [[package]] name = "zksync_prover_keystore" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "bincode", @@ -8948,7 +8948,7 @@ dependencies = [ [[package]] name = "zksync_vk_setup_data_generator_server_fri" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "bincode", @@ -9054,7 +9054,7 @@ dependencies = [ [[package]] name = "zksync_witness_generator" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", @@ -9092,7 +9092,7 @@ dependencies = [ [[package]] name = "zksync_witness_vector_generator" -version = "17.1.1" +version = "18.0.0" dependencies = [ "anyhow", "async-trait", diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 4e2483cbf0c0..7144b3821215 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/bin/*", "crates/lib/*"] resolver = "2" [workspace.package] -version = "17.1.1" # x-release-please-version +version = "18.0.0" # x-release-please-version edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -100,14 +100,14 @@ zksync_core_leftovers = { path = "../core/lib/zksync_core_leftovers" } zksync_protobuf_config = { path = "../core/lib/protobuf_config" } # Prover workspace dependencies -zksync_prover_dal = { version = "17.1.1", path = "crates/lib/prover_dal" } -zksync_prover_fri_types = { version = "17.1.1", path = "crates/lib/prover_fri_types" } -zksync_prover_fri_utils = { version = "17.1.1", path = "crates/lib/prover_fri_utils" } -zksync_prover_keystore = { version = "17.1.1", path = "crates/lib/keystore" } -zksync_vk_setup_data_generator_server_fri = { version = "17.1.1", path = "crates/bin/vk_setup_data_generator_server_fri" } -zksync_prover_job_processor = { version = "17.1.1", path = "crates/lib/prover_job_processor" } -zksync_circuit_prover_service = { version = "17.1.1", path = "crates/lib/circuit_prover_service" } -zksync_prover_job_monitor = { version = "17.1.1", path = "crates/bin/prover_job_monitor" } +zksync_prover_dal = { version = "18.0.0", path = "crates/lib/prover_dal" } +zksync_prover_fri_types = { version = "18.0.0", path = "crates/lib/prover_fri_types" } +zksync_prover_fri_utils = { version = "18.0.0", path = "crates/lib/prover_fri_utils" } +zksync_prover_keystore = { version = "18.0.0", path = "crates/lib/keystore" } +zksync_vk_setup_data_generator_server_fri = { version = "18.0.0", path = "crates/bin/vk_setup_data_generator_server_fri" } +zksync_prover_job_processor = { version = "18.0.0", path = "crates/lib/prover_job_processor" } +zksync_circuit_prover_service = { version = "18.0.0", path = "crates/lib/circuit_prover_service" } +zksync_prover_job_monitor = { version = "18.0.0", path = "crates/bin/prover_job_monitor" } # for `perf` profiling [profile.perf] From cd21c9ea31192889d83d85be549290cd99c7bba6 Mon Sep 17 00:00:00 2001 From: Marcin M <128217157+mm-zk@users.noreply.github.com> Date: Mon, 27 Jan 2025 15:12:45 +0100 Subject: [PATCH 38/52] feat: Updating information about keys & commitments for releases (#3486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ * Updating info on v25 and v26 (they are the same as v24.1) * Cleaned up README --- .../historical_data/0.25.0/commitments.json | 6 + .../snark_verification_scheduler_key.json | 399 ++++++++++++++++++ .../historical_data/0.26.0/commitments.json | 6 + .../snark_verification_scheduler_key.json | 399 ++++++++++++++++++ prover/data/historical_data/README.md | 19 +- 5 files changed, 822 insertions(+), 7 deletions(-) create mode 100644 prover/data/historical_data/0.25.0/commitments.json create mode 100644 prover/data/historical_data/0.25.0/snark_verification_scheduler_key.json create mode 100644 prover/data/historical_data/0.26.0/commitments.json create mode 100644 prover/data/historical_data/0.26.0/snark_verification_scheduler_key.json diff --git a/prover/data/historical_data/0.25.0/commitments.json b/prover/data/historical_data/0.25.0/commitments.json new file mode 100644 index 000000000000..086609a5822b --- /dev/null +++ b/prover/data/historical_data/0.25.0/commitments.json @@ -0,0 +1,6 @@ +{ + "leaf": "0xf9664f4324c1400fa5c3822d667f30e873f53f1b8033180cd15fe41c1e2355c6", + "node": "0xf520cd5b37e74e19fdb369c8d676a04dce8a19457497ac6686d2bb95d94109c8", + "scheduler": "0xe6ba9d6b042440c480fa1c7182be32387db6e90281e82f37398d3f98f63f098a", + "snark_wrapper": "0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2" +} \ No newline at end of file diff --git a/prover/data/historical_data/0.25.0/snark_verification_scheduler_key.json b/prover/data/historical_data/0.25.0/snark_verification_scheduler_key.json new file mode 100644 index 000000000000..acb7e3fe8969 --- /dev/null +++ b/prover/data/historical_data/0.25.0/snark_verification_scheduler_key.json @@ -0,0 +1,399 @@ +{ + "n": 16777215, + "num_inputs": 1, + "state_width": 4, + "num_witness_polys": 0, + "gate_setup_commitments": [ + { + "x": [ + 14543631136906534221, + 11532161447842416044, + 11114175029926010938, + 1228896787564295039 + ], + "y": [ + 13293602262342424489, + 8897930584356943159, + 13256028170406220369, + 3214939367598363288 + ], + "infinity": false + }, + { + "x": [ + 11488992528554025682, + 12016824828223971094, + 11942004360057333370, + 316831626296641307 + ], + "y": [ + 304673622018339856, + 7139037552557818730, + 12475560967982555143, + 1055588351918295250 + ], + "infinity": false + }, + { + "x": [ + 2274984630539920017, + 5398167177582250136, + 16440396753384808945, + 1037682586893548769 + ], + "y": [ + 10168660308952593373, + 16526369642614237721, + 569062739734175056, + 155645558476901406 + ], + "infinity": false + }, + { + "x": [ + 14005362797509427677, + 2662603874351919260, + 14261489165672308143, + 1470528288349794782 + ], + "y": [ + 11144229651170108862, + 11439490264313454962, + 114993091474760680, + 1037267173208738614 + ], + "infinity": false + }, + { + "x": [ + 10726125240955612787, + 1916320162213728495, + 1058608086768277905, + 1651114031905829493 + ], + "y": [ + 13237242732587628574, + 4774776044666137690, + 14401013098807103799, + 2514139699916115771 + ], + "infinity": false + }, + { + "x": [ + 14434760601334248377, + 5316938318287831815, + 6221098547630910324, + 980422841280734466 + ], + "y": [ + 9201886393750447942, + 3840149540273146267, + 18179910191622136829, + 1563809864380914603 + ], + "infinity": false + }, + { + "x": [ + 9586697317366528906, + 2325800863365957883, + 1243781259615311278, + 3048012003267036960 + ], + "y": [ + 612821620743617231, + 1510385666449513894, + 9368337288452385056, + 2949736812933507034 + ], + "infinity": false + }, + { + "x": [ + 11830690209042008764, + 11761396005838073769, + 18271188400274886574, + 2896734446482773484 + ], + "y": [ + 1890606551566554401, + 10220931290312275762, + 3256711195869515344, + 2466626485328709457 + ], + "infinity": false + } + ], + "gate_selectors_commitments": [ + { + "x": [ + 10865727529243127085, + 4083978853392244827, + 14303622309482785753, + 2263042021033673595 + ], + "y": [ + 3019601017411802529, + 880444282195426618, + 9998743525359587628, + 2891421025832200233 + ], + "infinity": false + }, + { + "x": [ + 5208608554346323426, + 8575970970223832576, + 2966209169082345602, + 239576408267301488 + ], + "y": [ + 17715084817752316452, + 2726293100894160682, + 17920596859559317135, + 3485576345363305439 + ], + "infinity": false + } + ], + "permutation_commitments": [ + { + "x": [ + 14761045450946573029, + 17157644513453531531, + 2555518804134782053, + 1415819224310783987 + ], + "y": [ + 17265629196749977462, + 4128711855633066822, + 8435602817910411328, + 1408116296902303196 + ], + "infinity": false + }, + { + "x": [ + 3307267823832528482, + 2406249680085831639, + 9091964031261402109, + 2846274000290842933 + ], + "y": [ + 17374905554931807856, + 6690578002079222163, + 11809376320193686210, + 2676076649992974574 + ], + "infinity": false + }, + { + "x": [ + 3159118708748226574, + 5508845413629697013, + 13350869305506486049, + 689297560178790472 + ], + "y": [ + 15696011303896469684, + 12551611148155235140, + 14438660833518031207, + 425021756161657108 + ], + "infinity": false + }, + { + "x": [ + 18349397811516917436, + 4473982696343317918, + 13070312540813307819, + 2109468484629113245 + ], + "y": [ + 13254534552549721008, + 17388411854346636521, + 17875890960520499518, + 1062184221180884481 + ], + "infinity": false + } + ], + "total_lookup_entries_length": 1787472, + "lookup_selector_commitment": { + "x": [ + 9324906502432882695, + 14977861238256290580, + 12538013124354067293, + 3408438202312564138 + ], + "y": [ + 14942105932194201701, + 12210090881357612547, + 14774705021036784261, + 2531694948512337448 + ], + "infinity": false + }, + "lookup_tables_commitments": [ + { + "x": [ + 10873859091125335643, + 3906092213625635374, + 17046157606087980048, + 3193402705223440293 + ], + "y": [ + 10158946293873382504, + 2171386304067884865, + 6918663094168980658, + 350601565475975409 + ], + "infinity": false + }, + { + "x": [ + 12822112641313049260, + 3646552465186399021, + 10324071010773924047, + 2209084192380614662 + ], + "y": [ + 11045141628975531869, + 12589678537679955590, + 3065046617868727674, + 2099447669854151830 + ], + "infinity": false + }, + { + "x": [ + 11395032673621937545, + 3000063650268118516, + 7857619430005721792, + 805706808484810738 + ], + "y": [ + 6817063666434679427, + 1646386051225388537, + 4677946977082722827, + 1369650305976868514 + ], + "infinity": false + }, + { + "x": [ + 2885179371868476351, + 159944842081142878, + 6092294387055034894, + 213843603626505240 + ], + "y": [ + 11868113133779277990, + 8509646480531194854, + 14088068011597639414, + 707070630614027545 + ], + "infinity": false + } + ], + "lookup_table_type_commitment": { + "x": [ + 1732877442096985191, + 7537030715658833452, + 14073502080301311448, + 2178792007727681099 + ], + "y": [ + 8513095304113652904, + 6581396660744182779, + 13939755637576387431, + 2477157044961106453 + ], + "infinity": false + }, + "non_residues": [ + [ + 5, + 0, + 0, + 0 + ], + [ + 7, + 0, + 0, + 0 + ], + [ + 10, + 0, + 0, + 0 + ] + ], + "g2_elements": [ + { + "x": { + "c0": [ + 5106727233969649389, + 7440829307424791261, + 4785637993704342649, + 1729627375292849782 + ], + "c1": [ + 10945020018377822914, + 17413811393473931026, + 8241798111626485029, + 1841571559660931130 + ] + }, + "y": { + "c0": [ + 5541340697920699818, + 16416156555105522555, + 5380518976772849807, + 1353435754470862315 + ], + "c1": [ + 6173549831154472795, + 13567992399387660019, + 17050234209342075797, + 650358724130500725 + ] + }, + "infinity": false + }, + { + "x": { + "c0": [ + 9089143573911733168, + 11482283522806384523, + 13585589533905622862, + 79029415676722370 + ], + "c1": [ + 5692040832573735873, + 16884514497384809355, + 16717166481813659368, + 2742131088506155463 + ] + }, + "y": { + "c0": [ + 9604638503594647125, + 1289961608472612514, + 6217038149984805214, + 2521661352385209130 + ], + "c1": [ + 17168069778630926308, + 11309277837895768996, + 15154989611154567813, + 359271377050603491 + ] + }, + "infinity": false + } + ] +} \ No newline at end of file diff --git a/prover/data/historical_data/0.26.0/commitments.json b/prover/data/historical_data/0.26.0/commitments.json new file mode 100644 index 000000000000..086609a5822b --- /dev/null +++ b/prover/data/historical_data/0.26.0/commitments.json @@ -0,0 +1,6 @@ +{ + "leaf": "0xf9664f4324c1400fa5c3822d667f30e873f53f1b8033180cd15fe41c1e2355c6", + "node": "0xf520cd5b37e74e19fdb369c8d676a04dce8a19457497ac6686d2bb95d94109c8", + "scheduler": "0xe6ba9d6b042440c480fa1c7182be32387db6e90281e82f37398d3f98f63f098a", + "snark_wrapper": "0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2" +} \ No newline at end of file diff --git a/prover/data/historical_data/0.26.0/snark_verification_scheduler_key.json b/prover/data/historical_data/0.26.0/snark_verification_scheduler_key.json new file mode 100644 index 000000000000..acb7e3fe8969 --- /dev/null +++ b/prover/data/historical_data/0.26.0/snark_verification_scheduler_key.json @@ -0,0 +1,399 @@ +{ + "n": 16777215, + "num_inputs": 1, + "state_width": 4, + "num_witness_polys": 0, + "gate_setup_commitments": [ + { + "x": [ + 14543631136906534221, + 11532161447842416044, + 11114175029926010938, + 1228896787564295039 + ], + "y": [ + 13293602262342424489, + 8897930584356943159, + 13256028170406220369, + 3214939367598363288 + ], + "infinity": false + }, + { + "x": [ + 11488992528554025682, + 12016824828223971094, + 11942004360057333370, + 316831626296641307 + ], + "y": [ + 304673622018339856, + 7139037552557818730, + 12475560967982555143, + 1055588351918295250 + ], + "infinity": false + }, + { + "x": [ + 2274984630539920017, + 5398167177582250136, + 16440396753384808945, + 1037682586893548769 + ], + "y": [ + 10168660308952593373, + 16526369642614237721, + 569062739734175056, + 155645558476901406 + ], + "infinity": false + }, + { + "x": [ + 14005362797509427677, + 2662603874351919260, + 14261489165672308143, + 1470528288349794782 + ], + "y": [ + 11144229651170108862, + 11439490264313454962, + 114993091474760680, + 1037267173208738614 + ], + "infinity": false + }, + { + "x": [ + 10726125240955612787, + 1916320162213728495, + 1058608086768277905, + 1651114031905829493 + ], + "y": [ + 13237242732587628574, + 4774776044666137690, + 14401013098807103799, + 2514139699916115771 + ], + "infinity": false + }, + { + "x": [ + 14434760601334248377, + 5316938318287831815, + 6221098547630910324, + 980422841280734466 + ], + "y": [ + 9201886393750447942, + 3840149540273146267, + 18179910191622136829, + 1563809864380914603 + ], + "infinity": false + }, + { + "x": [ + 9586697317366528906, + 2325800863365957883, + 1243781259615311278, + 3048012003267036960 + ], + "y": [ + 612821620743617231, + 1510385666449513894, + 9368337288452385056, + 2949736812933507034 + ], + "infinity": false + }, + { + "x": [ + 11830690209042008764, + 11761396005838073769, + 18271188400274886574, + 2896734446482773484 + ], + "y": [ + 1890606551566554401, + 10220931290312275762, + 3256711195869515344, + 2466626485328709457 + ], + "infinity": false + } + ], + "gate_selectors_commitments": [ + { + "x": [ + 10865727529243127085, + 4083978853392244827, + 14303622309482785753, + 2263042021033673595 + ], + "y": [ + 3019601017411802529, + 880444282195426618, + 9998743525359587628, + 2891421025832200233 + ], + "infinity": false + }, + { + "x": [ + 5208608554346323426, + 8575970970223832576, + 2966209169082345602, + 239576408267301488 + ], + "y": [ + 17715084817752316452, + 2726293100894160682, + 17920596859559317135, + 3485576345363305439 + ], + "infinity": false + } + ], + "permutation_commitments": [ + { + "x": [ + 14761045450946573029, + 17157644513453531531, + 2555518804134782053, + 1415819224310783987 + ], + "y": [ + 17265629196749977462, + 4128711855633066822, + 8435602817910411328, + 1408116296902303196 + ], + "infinity": false + }, + { + "x": [ + 3307267823832528482, + 2406249680085831639, + 9091964031261402109, + 2846274000290842933 + ], + "y": [ + 17374905554931807856, + 6690578002079222163, + 11809376320193686210, + 2676076649992974574 + ], + "infinity": false + }, + { + "x": [ + 3159118708748226574, + 5508845413629697013, + 13350869305506486049, + 689297560178790472 + ], + "y": [ + 15696011303896469684, + 12551611148155235140, + 14438660833518031207, + 425021756161657108 + ], + "infinity": false + }, + { + "x": [ + 18349397811516917436, + 4473982696343317918, + 13070312540813307819, + 2109468484629113245 + ], + "y": [ + 13254534552549721008, + 17388411854346636521, + 17875890960520499518, + 1062184221180884481 + ], + "infinity": false + } + ], + "total_lookup_entries_length": 1787472, + "lookup_selector_commitment": { + "x": [ + 9324906502432882695, + 14977861238256290580, + 12538013124354067293, + 3408438202312564138 + ], + "y": [ + 14942105932194201701, + 12210090881357612547, + 14774705021036784261, + 2531694948512337448 + ], + "infinity": false + }, + "lookup_tables_commitments": [ + { + "x": [ + 10873859091125335643, + 3906092213625635374, + 17046157606087980048, + 3193402705223440293 + ], + "y": [ + 10158946293873382504, + 2171386304067884865, + 6918663094168980658, + 350601565475975409 + ], + "infinity": false + }, + { + "x": [ + 12822112641313049260, + 3646552465186399021, + 10324071010773924047, + 2209084192380614662 + ], + "y": [ + 11045141628975531869, + 12589678537679955590, + 3065046617868727674, + 2099447669854151830 + ], + "infinity": false + }, + { + "x": [ + 11395032673621937545, + 3000063650268118516, + 7857619430005721792, + 805706808484810738 + ], + "y": [ + 6817063666434679427, + 1646386051225388537, + 4677946977082722827, + 1369650305976868514 + ], + "infinity": false + }, + { + "x": [ + 2885179371868476351, + 159944842081142878, + 6092294387055034894, + 213843603626505240 + ], + "y": [ + 11868113133779277990, + 8509646480531194854, + 14088068011597639414, + 707070630614027545 + ], + "infinity": false + } + ], + "lookup_table_type_commitment": { + "x": [ + 1732877442096985191, + 7537030715658833452, + 14073502080301311448, + 2178792007727681099 + ], + "y": [ + 8513095304113652904, + 6581396660744182779, + 13939755637576387431, + 2477157044961106453 + ], + "infinity": false + }, + "non_residues": [ + [ + 5, + 0, + 0, + 0 + ], + [ + 7, + 0, + 0, + 0 + ], + [ + 10, + 0, + 0, + 0 + ] + ], + "g2_elements": [ + { + "x": { + "c0": [ + 5106727233969649389, + 7440829307424791261, + 4785637993704342649, + 1729627375292849782 + ], + "c1": [ + 10945020018377822914, + 17413811393473931026, + 8241798111626485029, + 1841571559660931130 + ] + }, + "y": { + "c0": [ + 5541340697920699818, + 16416156555105522555, + 5380518976772849807, + 1353435754470862315 + ], + "c1": [ + 6173549831154472795, + 13567992399387660019, + 17050234209342075797, + 650358724130500725 + ] + }, + "infinity": false + }, + { + "x": { + "c0": [ + 9089143573911733168, + 11482283522806384523, + 13585589533905622862, + 79029415676722370 + ], + "c1": [ + 5692040832573735873, + 16884514497384809355, + 16717166481813659368, + 2742131088506155463 + ] + }, + "y": { + "c0": [ + 9604638503594647125, + 1289961608472612514, + 6217038149984805214, + 2521661352385209130 + ], + "c1": [ + 17168069778630926308, + 11309277837895768996, + 15154989611154567813, + 359271377050603491 + ] + }, + "infinity": false + } + ] +} \ No newline at end of file diff --git a/prover/data/historical_data/README.md b/prover/data/historical_data/README.md index 22df8acd3384..8d368e249fa1 100644 --- a/prover/data/historical_data/README.md +++ b/prover/data/historical_data/README.md @@ -3,12 +3,17 @@ This directory contains historical verification keys and hashes. The name of the subdirectory should match the protocol version. -- 18 - boojum - 1.4.0 -- 19 - boojum fix -- 20 - fee model - 1.4.1 -- 21 - blobs - 1.4.2 -- 22 - fix - 1.4.2 -- 23 - 16 blobs + AA hashes + shared bridge - 1.5.0 -- 24 - 23 + fixes +| Version | Description | Circuit version | Other | +| ------- | ------------------------------------ | --------------- | ------------------ | +| 18 | boojum - 1.4.0 | 1.4.0 | | +| 19 | boojum fix | | | +| 20 | fee model - 1.4.1 | 1.4.1 | | +| 21 | blobs - 1.4.2 | 1.4.2 | | +| 22 | fix - 1.4.2 | | | +| 23 | 16 blobs + AA hashes + shared bridge | 1.5.0 | | +| 24.0 | 23 + fixes | | | +| 24.1 | fixes | | | +| 25.0 | protocol defence | | no circuit changes | +| 26.0 | gateway & bridges | | no circuit changes | And from version 24, we switched to semver (so 0.24.0, 0.24.1 etc). From f25adefa4d0a863f9a572bc6bb5fdc31d3b469c2 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 28 Jan 2025 09:42:18 +0100 Subject: [PATCH 39/52] feat(nix): add nix packages for `zkstack` and `foundry-zksync`, update dev environments, and improve reproducibility (#3524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ - Introduced new functionalities via `zkstack.nix` and `foundry-zksync.nix`. - Optimized the `zksync` and `tee_prover` builds with clear sandboxing options and improved handling of files. ## Why ❔ - Updated `flake.nix` and other dependencies to ensure improved compatibility and reproducibility. - Enhanced development environment with `devShellAll.nix` for a full stack. ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] 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`. --------- Signed-off-by: Harald Hoyer --- etc/nix/README.md | 44 +++++--- etc/nix/container-tee-prover-azure.nix | 64 +++++++++++ etc/nix/container-tee-prover-dcap.nix | 17 +++ etc/nix/container-tee_prover.nix | 55 --------- etc/nix/{devshell.nix => devShell.nix} | 30 ++--- etc/nix/devShellAll.nix | 28 +++++ etc/nix/foundry-zksync.nix | 63 +++++++++++ etc/nix/tee_prover.nix | 16 ++- etc/nix/zkstack.nix | 50 +++++++++ etc/nix/zksync.nix | 45 ++++++-- flake.lock | 147 +++++++++---------------- flake.nix | 96 ++++++++-------- 12 files changed, 421 insertions(+), 234 deletions(-) create mode 100644 etc/nix/container-tee-prover-azure.nix create mode 100644 etc/nix/container-tee-prover-dcap.nix delete mode 100644 etc/nix/container-tee_prover.nix rename etc/nix/{devshell.nix => devShell.nix} (54%) create mode 100644 etc/nix/devShellAll.nix create mode 100644 etc/nix/foundry-zksync.nix create mode 100644 etc/nix/zkstack.nix diff --git a/etc/nix/README.md b/etc/nix/README.md index a7cce422e6e6..cd0339c5d224 100644 --- a/etc/nix/README.md +++ b/etc/nix/README.md @@ -1,8 +1,7 @@ # Declarative and Reproducible builds with Nix This directory contains the nix build recipes for various components of this project. Most importantly it is used to -reproducible build `zksync_tee_prover` reproducibly and create a container containing all what is needed to run it on an -SGX machine. +reproducibly build `zksync_tee_prover` and create a container containing all what is needed to run it on an SGX machine. ## Prerequisites @@ -32,34 +31,34 @@ or on nixos in `/etc/nixos/configuration.nix` add the following lines: Build various components of this project with `nix`. -### Build as the CI would +### Build as a CI would ```shell -nix run github:nixos/nixpkgs/nixos-23.11#nixci +nix run github:nixos/nixpkgs/nixos-24.11#nixci -- build -- --no-sandbox ``` ### Build individual parts ```shell -nix build .#zksync +nix build .#tee_prover +nix build .#container-tee-prover-dcap +nix build .#container-tee-prover-azure ``` -or +or `zksync`, which requires an internet connection while building (not reproducible) ```shell -nix build .#zksync.contract_verifier -nix build .#zksync.external_node -nix build .#zksync.server -nix build .#zksync.snapshots_creator -nix build .#zksync.block_reverter +nix build --no-sandbox .#zksync ``` or ```shell -nix build .#tee_prover -nix build .#container-tee-prover-dcap -nix build .#container-tee-prover-azure +nix build --no-sandbox .#zksync.contract_verifier +nix build --no-sandbox .#zksync.external_node +nix build --no-sandbox .#zksync.server +nix build --no-sandbox .#zksync.snapshots_creator +nix build --no-sandbox .#zksync.block_reverter ``` ## Develop @@ -79,6 +78,23 @@ EOF $ direnv allow ``` +### Full development stack + +If you also want `zkstack` and `foundry` you want to use: + +```shell +nix develop --no-sandbox .#devShellAll +``` + +optionally create `.envrc` for `direnv` to automatically load the environment when entering the main directory: + +```shell +$ cat < .envrc +use flake .#devShellAll --no-sandbox +EOF +$ direnv allow +``` + ### Format for commit ```shell diff --git a/etc/nix/container-tee-prover-azure.nix b/etc/nix/container-tee-prover-azure.nix new file mode 100644 index 000000000000..75462dd20de7 --- /dev/null +++ b/etc/nix/container-tee-prover-azure.nix @@ -0,0 +1,64 @@ +{ lib +, pkgs +, teepot +, tee_prover +, container-name ? "zksync-tee-prover-azure" +, isAzure ? true +, tag ? null +, ... +}: +let + name = container-name; + entrypoint = "${teepot.teepot.tee_key_preexec}/bin/tee-key-preexec"; +in +pkgs.lib.tee.sgxGramineContainer + { + inherit name; + inherit tag; + + packages = [ teepot.teepot.tee_key_preexec tee_prover ]; + inherit entrypoint; + inherit isAzure; + + manifest = { + loader = { + argv = [ + entrypoint + "--env-prefix" + "TEE_PROVER_" + "--" + "${tee_prover}/bin/zksync_tee_prover" + ]; + + log_level = "error"; + + env = { + TEE_PROVER_API_URL.passthrough = true; + TEE_PROVER_MAX_RETRIES.passthrough = true; + TEE_PROVER_INITIAL_RETRY_BACKOFF_SEC.passthrough = true; + TEE_PROVER_RETRY_BACKOFF_MULTIPLIER.passthrough = true; + TEE_PROVER_MAX_BACKOFF_SEC.passthrough = true; + API_PROMETHEUS_LISTENER_PORT.passthrough = true; + API_PROMETHEUS_PUSHGATEWAY_URL.passthrough = true; + API_PROMETHEUS_PUSH_INTERVAL_MS.passthrough = true; + + ### DEBUG ### + RUST_BACKTRACE = "1"; + RUST_LOG = "warning,zksync_tee_prover=debug"; + }; + }; + + sgx = { + edmm_enable = false; + enclave_size = "8G"; + max_threads = 128; + }; + }; + } // { + meta = { + description = "SGX on Azure container for the ZKsync TEE prover"; + homepage = "https://github.com/matter-labs/zksync-era/tree/main/core/bin/zksync_tee_prover"; + platforms = [ "x86_64-linux" ]; + license = [ lib.licenses.asl20 lib.licenses.mit ]; + }; +} diff --git a/etc/nix/container-tee-prover-dcap.nix b/etc/nix/container-tee-prover-dcap.nix new file mode 100644 index 000000000000..2da135c8432b --- /dev/null +++ b/etc/nix/container-tee-prover-dcap.nix @@ -0,0 +1,17 @@ +{ lib +, container-tee-prover-azure +, ... +}: container-tee-prover-azure.overrideAttrs + { + isAzure = false; + container-name = "zksync-tee-prover-dcap"; + } + // { + meta = { + description = "SGX DCAP container for the ZKsync TEE prover"; + homepage = "https://github.com/matter-labs/zksync-era/tree/main/core/bin/zksync_tee_prover"; + platforms = [ "x86_64-linux" ]; + license = [ lib.licenses.asl20 lib.licenses.mit ]; + }; +} + diff --git a/etc/nix/container-tee_prover.nix b/etc/nix/container-tee_prover.nix deleted file mode 100644 index cb8ebfb51549..000000000000 --- a/etc/nix/container-tee_prover.nix +++ /dev/null @@ -1,55 +0,0 @@ -{ pkgs -, nixsgxLib -, teepot -, tee_prover -, container-name -, isAzure ? true -, tag ? null -}: -let - name = container-name; - entrypoint = "${teepot.teepot.tee_key_preexec}/bin/tee-key-preexec"; -in -nixsgxLib.mkSGXContainer { - inherit name; - inherit tag; - - packages = [ teepot.teepot.tee_key_preexec tee_prover ]; - inherit entrypoint; - inherit isAzure; - - manifest = { - loader = { - argv = [ - entrypoint - "--env-prefix" - "TEE_PROVER_" - "--" - "${tee_prover}/bin/zksync_tee_prover" - ]; - - log_level = "error"; - - env = { - TEE_PROVER_API_URL.passthrough = true; - TEE_PROVER_MAX_RETRIES.passthrough = true; - TEE_PROVER_INITIAL_RETRY_BACKOFF_SEC.passthrough = true; - TEE_PROVER_RETRY_BACKOFF_MULTIPLIER.passthrough = true; - TEE_PROVER_MAX_BACKOFF_SEC.passthrough = true; - API_PROMETHEUS_LISTENER_PORT.passthrough = true; - API_PROMETHEUS_PUSHGATEWAY_URL.passthrough = true; - API_PROMETHEUS_PUSH_INTERVAL_MS.passthrough = true; - - ### DEBUG ### - RUST_BACKTRACE = "1"; - RUST_LOG = "warning,zksync_tee_prover=debug"; - }; - }; - - sgx = { - edmm_enable = false; - enclave_size = "8G"; - max_threads = 128; - }; - }; -} diff --git a/etc/nix/devshell.nix b/etc/nix/devShell.nix similarity index 54% rename from etc/nix/devshell.nix rename to etc/nix/devShell.nix index 046cd210d162..a63f6c656c2f 100644 --- a/etc/nix/devshell.nix +++ b/etc/nix/devShell.nix @@ -1,22 +1,21 @@ { pkgs -, zksync -, commonArgs +, tee_prover +, coreCommonArgs +, inputs +, ... }: -pkgs.mkShell { - inputsFrom = [ zksync ]; +let + toolchain = pkgs.rust-bin.fromRustupToolchainFile (inputs.src + "/rust-toolchain"); - packages = with pkgs; [ - docker-compose - nodejs - yarn - axel - postgresql - python3 - solc - sqlx-cli - ]; + toolchain_with_src = (toolchain.override { + extensions = [ "rustfmt" "clippy" "rust-src" ]; + }); +in +pkgs.mkShell { + inputsFrom = [ tee_prover ]; + packages = [ ]; - inherit (commonArgs) env hardeningEnable; + inherit (coreCommonArgs) env hardeningEnable; shellHook = '' export ZKSYNC_HOME=$PWD @@ -32,6 +31,7 @@ pkgs.mkShell { fi ''; + RUST_SRC_PATH = "${toolchain_with_src}/lib/rustlib/src/rust/library"; ZK_NIX_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ ]; } diff --git a/etc/nix/devShellAll.nix b/etc/nix/devShellAll.nix new file mode 100644 index 000000000000..ed043c30b2eb --- /dev/null +++ b/etc/nix/devShellAll.nix @@ -0,0 +1,28 @@ +{ pkgs +, zksync +, zkstack +, devShell +, foundry-zksync +, ... +}: +let + newshell = (pkgs.mkShell { + inputsFrom = [ zksync zkstack ]; + + packages = with pkgs; [ + docker-compose + nodejs + yarn + axel + postgresql + python3 + solc + sqlx-cli + zkstack + foundry-zksync + nodePackages.prettier + ]; + }); +in +devShell.overrideAttrs + (old: { inherit (newshell) buildInputs nativeBuildInputs; }) diff --git a/etc/nix/foundry-zksync.nix b/etc/nix/foundry-zksync.nix new file mode 100644 index 000000000000..765b019fac3d --- /dev/null +++ b/etc/nix/foundry-zksync.nix @@ -0,0 +1,63 @@ +{ pkgs +, lib +, fetchFromGitHub +, inputs +, ... +}: +let + src = fetchFromGitHub { + owner = "matter-labs"; + repo = "foundry-zksync"; + tag = "0.2.0-zksync.0.0.2"; + hash = "sha256-SEIpt/kQSuYbR0w/DFeXXLMlT7YLP6T7tDDuRioUWlA="; + }; + + toolchain = pkgs.rust-bin.fromRustupToolchainFile "${src}/rust-toolchain"; + + craneLib = (inputs.crane.mkLib pkgs).overrideToolchain toolchain; + + rustPlatform = pkgs.makeRustPlatform { + cargo = toolchain; + rustc = toolchain; + }; +in +craneLib.buildPackage { + # Some crates download stuff from the network while compiling!!!! + # Allows derivation to access network + # + # Users of this package must set options to indicate that the sandbox conditions can be relaxed for this package. + # These are: + # - When used in a flake, set the flake's config with this line: nixConfig.sandbox = false; + # - From the command line with nix , add one of these options: + # - --option sandbox false + # - --no-sandbox + __noChroot = true; + + pname = src.repo; + version = src.tag; + inherit src; + + doCheck = false; + + nativeBuildInputs = with pkgs;[ + pkg-config + rustPlatform.bindgenHook + ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.DarwinTools ]; + + buildInputs = with pkgs;[ + libusb1.dev + libclang.dev + openssl.dev + lz4.dev + bzip2.dev + rocksdb_8_3 + snappy.dev + ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.apple_sdk.frameworks.AppKit ]; + + env = { + OPENSSL_NO_VENDOR = "1"; + ROCKSDB_LIB_DIR = "${pkgs.rocksdb_8_3.out}/lib"; + ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb_8_3.out}/include"; + SNAPPY_LIB_DIR = "${pkgs.snappy.out}/lib"; + }; +} diff --git a/etc/nix/tee_prover.nix b/etc/nix/tee_prover.nix index 5811297ce854..315eaf9e169d 100644 --- a/etc/nix/tee_prover.nix +++ b/etc/nix/tee_prover.nix @@ -1,16 +1,17 @@ { craneLib -, commonArgs +, coreCommonArgs +, ... }: let pname = "zksync_tee_prover"; cargoExtraArgs = "--locked -p zksync_tee_prover"; in -craneLib.buildPackage (commonArgs // { +craneLib.buildPackage (coreCommonArgs // { inherit pname; version = (builtins.fromTOML (builtins.readFile ../../core/Cargo.toml)).workspace.package.version; inherit cargoExtraArgs; - cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { + cargoArtifacts = craneLib.buildDepsOnly (coreCommonArgs // { inherit pname; inherit cargoExtraArgs; }); @@ -18,4 +19,13 @@ craneLib.buildPackage (commonArgs // { postInstall = '' strip $out/bin/zksync_tee_prover ''; + + # zksync-protobuf has store paths + postPatch = '' + mkdir -p "$TMPDIR/nix-vendor" + cp -Lr "$cargoVendorDir" -T "$TMPDIR/nix-vendor" + sed -i "s|$cargoVendorDir|$TMPDIR/nix-vendor/|g" "$TMPDIR/nix-vendor/config.toml" + chmod -R +w "$TMPDIR/nix-vendor" + cargoVendorDir="$TMPDIR/nix-vendor" + ''; }) diff --git a/etc/nix/zkstack.nix b/etc/nix/zkstack.nix new file mode 100644 index 000000000000..0ec960da3a13 --- /dev/null +++ b/etc/nix/zkstack.nix @@ -0,0 +1,50 @@ +{ craneLib +, zkstackArgs +, ... +}: +craneLib.buildPackage (zkstackArgs // { + # Some crates download stuff from the network while compiling!!!! + # Allows derivation to access the network + # + # Users of this package must set options to indicate that the sandbox conditions can be relaxed for this package. + # These are: + # - In the appropriate nix.conf file (depends on multi vs single user nix installation), add the line: sandbox = relaxed + # - When used in a flake, set the flake's config with this line: nixConfig.sandbox = "relaxed"; + # - Same as above, but disabling the sandbox completely: nixConfig.sandbox = false; + # - From the command line with nix , add one of these options: + # - --option sandbox relaxed + # - --option sandbox false + # - --no-sandbox + # - --relaxed-sandbox + __noChroot = true; + cargoToml = "${zkstackArgs.src}/zkstack_cli/Cargo.toml"; + cargoLock = "${zkstackArgs.src}/zkstack_cli/Cargo.lock"; + + pname = "zkstack"; + + cargoArtifacts = craneLib.buildDepsOnly (zkstackArgs // { + pname = "zkstack-workspace"; + cargoToml = "${zkstackArgs.src}/zkstack_cli/Cargo.toml"; + cargoLock = "${zkstackArgs.src}/zkstack_cli/Cargo.lock"; + postUnpack = '' + cd $sourceRoot/zkstack_cli + sourceRoot="." + ''; + }); + + version = (builtins.fromTOML (builtins.readFile "${zkstackArgs.src}/zkstack_cli/Cargo.toml")).workspace.package.version; + + postUnpack = '' + cd $sourceRoot/zkstack_cli + sourceRoot="." + ''; + + # zksync-protobuf has store paths + postPatch = '' + mkdir -p "$TMPDIR/nix-vendor" + cp -Lr "$cargoVendorDir" -T "$TMPDIR/nix-vendor" + sed -i "s|$cargoVendorDir|$TMPDIR/nix-vendor/|g" "$TMPDIR/nix-vendor/config.toml" + chmod -R +w "$TMPDIR/nix-vendor" + cargoVendorDir="$TMPDIR/nix-vendor" + ''; +}) diff --git a/etc/nix/zksync.nix b/etc/nix/zksync.nix index 16d452c01bfd..cb002d1b918d 100644 --- a/etc/nix/zksync.nix +++ b/etc/nix/zksync.nix @@ -1,14 +1,32 @@ { craneLib -, commonArgs +, coreCommonArgs +, zkstack +, foundry-zksync +, ... }: -craneLib.buildPackage (commonArgs // { +let + cargoExtraArgs = "--locked --bin zksync_server --bin zksync_contract_verifier --bin zksync_external_node --bin snapshots_creator --bin block_reverter --bin merkle_tree_consistency_checker"; +in +craneLib.buildPackage (coreCommonArgs // { + # Some crates download stuff from the network while compiling!!!! + # Allows derivation to access network + # + # Users of this package must set options to indicate that the sandbox conditions can be relaxed for this package. + # These are: + # - When used in a flake, set the flake's config with this line: nixConfig.sandbox = false; + # - From the command line with nix , add one of these options: + # - --option sandbox false + # - --no-sandbox + __noChroot = true; + pname = "zksync"; - version = (builtins.fromTOML (builtins.readFile ../../core/Cargo.toml)).workspace.package.version; - cargoExtraArgs = "--all"; + version = (builtins.fromTOML (builtins.readFile (coreCommonArgs.src + "/Cargo.toml"))).workspace.package.version; + inherit cargoExtraArgs; - cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { - pname = "zksync-era-workspace"; - }); + buildInputs = coreCommonArgs.buildInputs ++ [ + zkstack + foundry-zksync + ]; outputs = [ "out" @@ -39,4 +57,15 @@ craneLib.buildPackage (commonArgs // { mkdir -p $server/nix-support echo "block_reverter" >> $server/nix-support/propagated-user-env-packages ''; -}) + + # zksync-protobuf has store paths + postPatch = '' + mkdir -p "$TMPDIR/nix-vendor" + cp -Lr "$cargoVendorDir" -T "$TMPDIR/nix-vendor" + sed -i "s|$cargoVendorDir|$TMPDIR/nix-vendor/|g" "$TMPDIR/nix-vendor/config.toml" + chmod -R +w "$TMPDIR/nix-vendor" + cargoVendorDir="$TMPDIR/nix-vendor" + ''; +} +) + diff --git a/flake.lock b/flake.lock index e1905f2a1f65..43bcf9c2b42a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,17 +1,12 @@ { "nodes": { "crane": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, "locked": { - "lastModified": 1722960479, - "narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=", + "lastModified": 1737250794, + "narHash": "sha256-bdIPhvsAKyYQzqAIeay4kOxTHGwLGkhM+IlBIsmMYFI=", "owner": "ipetkov", "repo": "crane", - "rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4", + "rev": "c5b7075f4a6d523fe8204618aa9754e56478c0e0", "type": "github" }, "original": { @@ -21,23 +16,17 @@ } }, "crane_2": { - "inputs": { - "nixpkgs": [ - "teepot-flake", - "nixsgx-flake", - "nixpkgs" - ] - }, "locked": { - "lastModified": 1716156051, - "narHash": "sha256-TjUX7WWRcrhuUxDHsR8pDR2N7jitqZehgCVSy3kBeS8=", + "lastModified": 1731974531, + "narHash": "sha256-z7hiGBWsbWwSnu5UMmYyfHEehlSmfB8sCA8iH4nmxm8=", "owner": "ipetkov", "repo": "crane", - "rev": "7443df1c478947bf96a2e699209f53b2db26209d", + "rev": "8ff9c457d60951bdd37a05ae903423de7ff55c6e", "type": "github" }, "original": { "owner": "ipetkov", + "ref": "8ff9c457d60951bdd37a05ae903423de7ff55c6e", "repo": "crane", "type": "github" } @@ -95,11 +84,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -148,7 +137,7 @@ }, "flake-utils-plus_3": { "inputs": { - "flake-utils": "flake-utils_6" + "flake-utils": "flake-utils_5" }, "locked": { "lastModified": 1715533576, @@ -205,24 +194,6 @@ "inputs": { "systems": "systems_4" }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_5": { - "inputs": { - "systems": "systems_5" - }, "locked": { "lastModified": 1710146030, "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", @@ -237,9 +208,9 @@ "type": "github" } }, - "flake-utils_6": { + "flake-utils_5": { "inputs": { - "systems": "systems_6" + "systems": "systems_5" }, "locked": { "lastModified": 1694529238, @@ -257,43 +228,43 @@ }, "nixpkgs": { "locked": { - "lastModified": 1722869614, - "narHash": "sha256-7ojM1KSk3mzutD7SkrdSflHXEujPvW1u7QuqWoTLXQU=", + "lastModified": 1737404927, + "narHash": "sha256-e1WgPJpIYbOuokjgylcsuoEUCB4Jl2rQXa2LUD6XAG8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "883180e6550c1723395a3a342f830bfc5c371f6b", + "rev": "ae584d90cbd0396a422289ee3efb1f1c9d141dc3", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1719707984, - "narHash": "sha256-RoxIr/fbndtuKqulGvNCcuzC6KdAib85Q8gXnjzA1dw=", + "lastModified": 1733550349, + "narHash": "sha256-NcGumB4Lr6KSDq+nIqXtNA8QwAQKDSZT7N9OTGWbTrs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7dca15289a1c2990efbe4680f0923ce14139b042", + "rev": "e2605d0744c2417b09f8bf850dfca42fcf537d34", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_3": { "locked": { - "lastModified": 1718428119, - "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "lastModified": 1736320768, + "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", "type": "github" }, "original": { @@ -305,16 +276,16 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1719707984, - "narHash": "sha256-RoxIr/fbndtuKqulGvNCcuzC6KdAib85Q8gXnjzA1dw=", + "lastModified": 1733550349, + "narHash": "sha256-NcGumB4Lr6KSDq+nIqXtNA8QwAQKDSZT7N9OTGWbTrs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7dca15289a1c2990efbe4680f0923ce14139b042", + "rev": "e2605d0744c2417b09f8bf850dfca42fcf537d34", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } @@ -341,11 +312,11 @@ "snowfall-lib": "snowfall-lib" }, "locked": { - "lastModified": 1721741092, - "narHash": "sha256-ghFoP5gZpc1i4I4PiVCH00QNZ6s6ipGUcA0P1TsSSC8=", + "lastModified": 1733824290, + "narHash": "sha256-8MKgW3pFW+IxsM/iGfHio91Gym89gh9uNQ3JO4+D8QY=", "owner": "matter-labs", "repo": "nixsgx", - "rev": "be2c19592d0d5601184c52c07ab6d88dec07ffd6", + "rev": "788ff5233053a52421c9f8fa626a936785dda511", "type": "github" }, "original": { @@ -360,11 +331,11 @@ "snowfall-lib": "snowfall-lib_2" }, "locked": { - "lastModified": 1723120465, - "narHash": "sha256-sWu5lKy71hHnSwydhwzG2XgSehjvLfK2iuUtNimvGkg=", + "lastModified": 1733824290, + "narHash": "sha256-8MKgW3pFW+IxsM/iGfHio91Gym89gh9uNQ3JO4+D8QY=", "owner": "matter-labs", "repo": "nixsgx", - "rev": "b080c32f2aa8b3d4b4bc4356a8a513279b6f82ab", + "rev": "788ff5233053a52421c9f8fa626a936785dda511", "type": "github" }, "original": { @@ -379,11 +350,11 @@ "snowfall-lib": "snowfall-lib_3" }, "locked": { - "lastModified": 1717758565, - "narHash": "sha256-yscuZ3ixjwTkqS6ew5cB3Uvy9e807szRlMoPSyQuRJM=", + "lastModified": 1719403531, + "narHash": "sha256-JYqPdAB393YZIndGs5om7EsLUha3fpLckb9RKjKN7Fg=", "owner": "matter-labs", "repo": "nixsgx", - "rev": "49a1ae79d92ccb6ed7cabfe5c5042b1399e3cd3e", + "rev": "3a272950fa21601f31e8ca8b4e4897975069a00a", "type": "github" }, "original": { @@ -407,11 +378,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1722997267, - "narHash": "sha256-8Pncp8IKd0f0N711CRrCGTC4iLfBE+/5kaMqyWxnYic=", + "lastModified": 1737512878, + "narHash": "sha256-dgF6htdmfNnZzVInifks6npnCAyVsIHWSpWNs10RSW0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "d720bf3cebac38c2426d77ee2e59943012854cb8", + "rev": "06b8ed0eee289fe94c66f1202ced9a6a2c59a14c", "type": "github" }, "original": { @@ -422,7 +393,6 @@ }, "rust-overlay_2": { "inputs": { - "flake-utils": "flake-utils_4", "nixpkgs": [ "teepot-flake", "nixsgx-flake", @@ -430,11 +400,11 @@ ] }, "locked": { - "lastModified": 1717985971, - "narHash": "sha256-24h/qKp0aeI+Ew13WdRF521kY24PYa5HOvw0mlrABjk=", + "lastModified": 1734661750, + "narHash": "sha256-BI58NBdimxu1lnpOrG9XxBz7Cwqy+qIf99zunWofX5w=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "abfe5b3126b1b7e9e4daafc1c6478d17f0b584e7", + "rev": "7d3d910d5fd575e6e8c5600d83d54e5c47273bfe", "type": "github" }, "original": { @@ -462,6 +432,7 @@ }, "original": { "owner": "snowfallorg", + "ref": "c6238c83de101729c5de3a29586ba166a9a65622", "repo": "lib", "type": "github" } @@ -486,6 +457,7 @@ }, "original": { "owner": "snowfallorg", + "ref": "c6238c83de101729c5de3a29586ba166a9a65622", "repo": "lib", "type": "github" } @@ -590,21 +562,6 @@ "type": "github" } }, - "systems_6": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "teepot-flake": { "inputs": { "crane": "crane_2", @@ -623,11 +580,11 @@ "vault-auth-tee-flake": "vault-auth-tee-flake" }, "locked": { - "lastModified": 1725354393, - "narHash": "sha256-RSiDY3sr0hdlydO3cYtidjVx+OlqIsmcnvsSDSGQPF0=", + "lastModified": 1737116236, + "narHash": "sha256-Bk52s9ENa6zbdaog6YZXMA720K6E+IRDqwYMi5NOWa0=", "owner": "matter-labs", "repo": "teepot", - "rev": "2c21d0161e43dc7a786787c89b84ecd6e8857106", + "rev": "e2c31919c92c5aa803fa3ce75824bea421bb3480", "type": "github" }, "original": { @@ -638,7 +595,7 @@ }, "vault-auth-tee-flake": { "inputs": { - "flake-utils": "flake-utils_5", + "flake-utils": "flake-utils_4", "nixpkgs": [ "teepot-flake", "nixsgx-flake", @@ -647,11 +604,11 @@ "nixsgx-flake": "nixsgx-flake_3" }, "locked": { - "lastModified": 1718012107, - "narHash": "sha256-uKiUBaEOj9f3NCn6oTw5VqoZJxsTXSoAn2IWVB/LSS0=", + "lastModified": 1719832445, + "narHash": "sha256-Dnueq3A1sf8zT+bY6CcuaxPvX4AK7B6Sveqb8YfoY8o=", "owner": "matter-labs", "repo": "vault-auth-tee", - "rev": "b10204436bc2fbad74c5716bd265fad74acc197c", + "rev": "2b53a4387fc8ecfb7826acd93d4895e7e810677d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 630d719aa4df..2e1e4577ef43 100644 --- a/flake.nix +++ b/flake.nix @@ -12,20 +12,16 @@ }; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; teepot-flake.url = "github:matter-labs/teepot"; nixsgx-flake.url = "github:matter-labs/nixsgx"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay.url = "github:oxalica/rust-overlay"; - crane = { - url = "github:ipetkov/crane?tag=v0.17.3"; - inputs.nixpkgs.follows = "nixpkgs"; - }; + crane.url = "github:ipetkov/crane?tag=v0.20.0"; }; - outputs = { self, nixpkgs, teepot-flake, nixsgx-flake, flake-utils, rust-overlay, crane }: + outputs = { self, nixpkgs, teepot-flake, nixsgx-flake, flake-utils, rust-overlay, crane } @ inputs: let - officialRelease = false; hardeningEnable = [ "fortify3" "pie" "relro" ]; out = system: @@ -46,11 +42,16 @@ packages = { # to ease potential cross-compilation, the overlay is used - inherit (appliedOverlay.zksync-era) zksync tee_prover container-tee-prover-azure container-tee-prover-dcap; + inherit (appliedOverlay.zksync-era) zksync tee_prover zkstack foundry-zksync; default = appliedOverlay.zksync-era.tee_prover; - }; + } // (pkgs.lib.optionalAttrs (pkgs.stdenv.hostPlatform.isx86_64 && pkgs.stdenv.hostPlatform.isLinux) { + inherit (appliedOverlay.zksync-era) container-tee-prover-azure container-tee-prover-dcap; + }); - devShells.default = appliedOverlay.zksync-era.devShell; + devShells = { + inherit (appliedOverlay.zksync-era) devShell devShellAll; + default = appliedOverlay.zksync-era.devShell; + }; }; in flake-utils.lib.eachDefaultSystem out // { @@ -59,16 +60,16 @@ let pkgs = final; - rustVersion = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain; + toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain; rustPlatform = pkgs.makeRustPlatform { - cargo = rustVersion; - rustc = rustVersion; + cargo = toolchain; + rustc = toolchain; }; - craneLib = (crane.mkLib pkgs).overrideToolchain rustVersion; + craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; - commonArgs = { + coreCommonArgs = { nativeBuildInputs = with pkgs;[ pkg-config rustPlatform.bindgenHook @@ -80,15 +81,25 @@ snappy.dev lz4.dev bzip2.dev - rocksdb + rocksdb_8_3 snappy.dev ]; - src = ./core/.; + src = with pkgs.lib.fileset; let root = ./core/.; in toSource { + inherit root; + fileset = unions [ + # Default files from crane (Rust and cargo files) + (craneLib.fileset.commonCargoSources root) + # proto files and friends + (fileFilter (file: file.hasExt "proto" || file.hasExt "js" || file.hasExt "ts" || file.hasExt "map" || file.hasExt "json") root) + (maybeMissing ./core/lib/dal/.) + ]; + }; env = { OPENSSL_NO_VENDOR = "1"; - ROCKSDB_LIB_DIR = "${pkgs.rocksdb.out}/lib"; + ROCKSDB_LIB_DIR = "${pkgs.rocksdb_8_3.out}/lib"; + ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb_8_3.out}/include"; SNAPPY_LIB_DIR = "${pkgs.snappy.out}/lib"; NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa"; }; @@ -97,35 +108,32 @@ strictDeps = true; inherit hardeningEnable; }; - in - { - zksync-era = rec { - devShell = pkgs.callPackage ./etc/nix/devshell.nix { - inherit zksync; - inherit commonArgs; - }; - zksync = pkgs.callPackage ./etc/nix/zksync.nix { - inherit craneLib; - inherit commonArgs; - }; - - tee_prover = pkgs.callPackage ./etc/nix/tee_prover.nix { - inherit craneLib; - inherit commonArgs; - }; - - container-tee-prover-azure = pkgs.callPackage ./etc/nix/container-tee_prover.nix { - inherit tee_prover; - isAzure = true; - container-name = "zksync-tee-prover-azure"; - }; - container-tee-prover-dcap = pkgs.callPackage ./etc/nix/container-tee_prover.nix { - inherit tee_prover; - isAzure = false; - container-name = "zksync-tee-prover-dcap"; + zkstackArgs = coreCommonArgs // { + src = with pkgs.lib.fileset; let root = ./.; in toSource { + inherit root; + fileset = unions [ + # Default files from crane (Rust and cargo files) + (craneLib.fileset.commonCargoSources root) + # proto files and friends + (fileFilter (file: file.hasExt "proto" || file.hasExt "js" || file.hasExt "ts" || file.hasExt "map" || file.hasExt "json") ./.) + (maybeMissing ./core/lib/dal/.) + ]; }; }; + in + { + zksync-era = pkgs.lib.makeScope pkgs.newScope ( + self: pkgs.lib.filesystem.packagesFromDirectoryRecursive { + callPackage = package: params: self.callPackage package (params // { + inherit craneLib; + inherit coreCommonArgs; + inherit zkstackArgs; + inputs = inputs // { src = ./.; }; + }); + directory = ./etc/nix; + } + ); }; }; } From 1d1e691a646ea5fd2c85ca783d23857ba168c2c9 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 28 Jan 2025 11:17:58 +0200 Subject: [PATCH 40/52] refactor(zk_toolbox): Minimize dependency on `zksync_config` (#3436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Largely removes `zkstack` dependency on `zksync_config`. ## Why ❔ This dependency significantly complicates new config system integration. While the alternative is less typesafe, it can be made more typesafe in the future once the config system integration is complete (a node binary can then be used to validate the changed config values). ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- .github/workflows/ci-core-reusable.yml | 2 +- zkstack_cli/Cargo.lock | 24 +-- zkstack_cli/Cargo.toml | 1 - zkstack_cli/crates/config/Cargo.toml | 6 +- zkstack_cli/crates/config/src/chain.rs | 39 ++--- .../crates/config/src/consensus_config.rs | 18 -- .../crates/config/src/consensus_secrets.rs | 14 -- zkstack_cli/crates/config/src/da.rs | 39 +++++ .../crates/config/src/external_node.rs | 28 ---- .../forge_interface/deploy_ecosystem/input.rs | 48 ++++-- .../deploy_gateway_ctm/input.rs | 18 +- .../deploy_l2_contracts/input.rs | 25 ++- .../gateway_chain_upgrade/input.rs | 18 +- .../gateway_ecosystem_upgrade/input.rs | 17 +- zkstack_cli/crates/config/src/general.rs | 157 +++++++----------- zkstack_cli/crates/config/src/genesis.rs | 42 +---- zkstack_cli/crates/config/src/lib.rs | 19 +-- zkstack_cli/crates/config/src/raw.rs | 156 +++++++++++++++++ zkstack_cli/crates/config/src/secrets.rs | 63 ++----- zkstack_cli/crates/config/src/traits.rs | 5 - zkstack_cli/crates/zkstack/Cargo.toml | 4 +- zkstack_cli/crates/zkstack/build.rs | 9 - .../crates/zkstack/completion/_zkstack.zsh | 4 +- .../crates/zkstack/completion/zkstack.fish | 4 +- .../commands/chain/accept_chain_ownership.rs | 12 +- .../src/commands/chain/args/genesis.rs | 14 +- .../commands/chain/args/init/da_configs.rs | 7 +- .../src/commands/chain/build_transactions.rs | 3 +- .../src/commands/chain/convert_to_gateway.rs | 39 ++--- .../zkstack/src/commands/chain/create.rs | 27 +-- .../src/commands/chain/deploy_l2_contracts.rs | 20 +-- .../src/commands/chain/deploy_paymaster.rs | 13 +- .../src/commands/chain/enable_evm_emulator.rs | 24 ++- .../src/commands/chain/gateway_upgrade.rs | 71 +++----- .../src/commands/chain/genesis/database.rs | 22 +-- .../zkstack/src/commands/chain/genesis/mod.rs | 4 +- .../src/commands/chain/genesis/server.rs | 8 +- .../src/commands/chain/init/configs.rs | 78 ++++----- .../zkstack/src/commands/chain/init/mod.rs | 27 +-- .../commands/chain/migrate_from_gateway.rs | 93 +++-------- .../src/commands/chain/migrate_to_gateway.rs | 90 +++------- .../crates/zkstack/src/commands/chain/mod.rs | 2 +- .../src/commands/chain/register_chain.rs | 14 +- .../chain/set_token_multiplier_setter.rs | 15 +- .../src/commands/chain/setup_legacy_bridge.rs | 13 +- .../zkstack/src/commands/consensus/conv.rs | 47 ------ .../zkstack/src/commands/consensus/mod.rs | 150 +++++++++++------ .../src/commands/consensus/proto/mod.proto | 9 - .../src/commands/consensus/proto/mod.rs | 6 - .../zkstack/src/commands/consensus/tests.rs | 19 --- .../src/commands/contract_verifier/wait.rs | 8 +- .../dev/commands/database/check_sqlx_data.rs | 4 +- .../commands/dev/commands/database/drop.rs | 2 +- .../commands/dev/commands/database/migrate.rs | 4 +- .../src/commands/dev/commands/database/mod.rs | 10 +- .../dev/commands/database/new_migration.rs | 6 +- .../commands/dev/commands/database/prepare.rs | 4 +- .../commands/dev/commands/database/reset.rs | 2 +- .../commands/dev/commands/database/setup.rs | 4 +- .../src/commands/dev/commands/genesis.rs | 2 +- .../src/commands/dev/commands/prover/info.rs | 9 +- .../src/commands/dev/commands/status/args.rs | 16 +- .../src/commands/dev/commands/status/mod.rs | 2 +- .../commands/dev/commands/test/loadtest.rs | 23 +-- .../src/commands/dev/commands/test/mod.rs | 2 +- .../src/commands/dev/commands/test/rust.rs | 19 +-- .../src/commands/dev/commands/test/utils.rs | 9 +- .../crates/zkstack/src/commands/dev/dals.rs | 43 ++--- .../zkstack/src/commands/dev/messages.rs | 3 - .../zkstack/src/commands/ecosystem/common.rs | 17 +- .../zkstack/src/commands/ecosystem/create.rs | 8 +- .../src/commands/ecosystem/gateway_upgrade.rs | 19 ++- .../zkstack/src/commands/ecosystem/mod.rs | 2 +- .../zkstack/src/commands/explorer/init.rs | 24 +-- .../src/commands/external_node/init.rs | 31 ++-- .../zkstack/src/commands/external_node/mod.rs | 2 +- .../commands/external_node/prepare_configs.rs | 151 +++++++---------- .../src/commands/external_node/wait.rs | 12 +- .../crates/zkstack/src/commands/portal.rs | 28 ++-- .../commands/prover/args/compressor_keys.rs | 8 +- .../zkstack/src/commands/prover/args/init.rs | 37 ++--- .../src/commands/prover/compressor_keys.rs | 46 ++--- .../zkstack/src/commands/prover/init.rs | 101 +++++++---- .../crates/zkstack/src/commands/prover/run.rs | 17 +- .../crates/zkstack/src/commands/server.rs | 18 +- .../crates/zkstack/src/commands/update.rs | 22 ++- .../crates/zkstack/src/external_node.rs | 14 +- zkstack_cli/crates/zkstack/src/messages.rs | 10 -- .../crates/zkstack/src/utils/consensus.rs | 141 +++++++++------- 89 files changed, 1101 insertions(+), 1366 deletions(-) delete mode 100644 zkstack_cli/crates/config/src/consensus_config.rs delete mode 100644 zkstack_cli/crates/config/src/consensus_secrets.rs create mode 100644 zkstack_cli/crates/config/src/da.rs delete mode 100644 zkstack_cli/crates/config/src/external_node.rs create mode 100644 zkstack_cli/crates/config/src/raw.rs delete mode 100644 zkstack_cli/crates/zkstack/src/commands/consensus/conv.rs delete mode 100644 zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.proto delete mode 100644 zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.rs delete mode 100644 zkstack_cli/crates/zkstack/src/commands/consensus/tests.rs diff --git a/.github/workflows/ci-core-reusable.yml b/.github/workflows/ci-core-reusable.yml index 6be2a05e52e2..1127204c552f 100644 --- a/.github/workflows/ci-core-reusable.yml +++ b/.github/workflows/ci-core-reusable.yml @@ -456,7 +456,7 @@ jobs: - name: Set up attester committee for the consensus chain run: | ci_run zkstack consensus wait-for-registry --ignore-prerequisites --verbose --chain consensus - ci_run zkstack consensus set-attester-committee --chain consensus --from-genesis &> ${{ env.INTEGRATION_TESTS_LOGS_DIR }}/consensus.log + ci_run zkstack consensus set-attester-committee --chain consensus --ignore-prerequisites --verbose --from-genesis &> ${{ env.INTEGRATION_TESTS_LOGS_DIR }}/consensus.log - name: Run integration tests run: | diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index d3691b9c6e05..f575dfcd4eab 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -7084,8 +7084,6 @@ dependencies = [ "zksync_contracts", "zksync_eth_client", "zksync_protobuf", - "zksync_protobuf_build", - "zksync_protobuf_config", "zksync_system_constants", "zksync_types", "zksync_web3_decl", @@ -7133,6 +7131,7 @@ dependencies = [ "serde_yaml", "strum", "thiserror", + "tokio", "url", "xshell", "zkstack_cli_common", @@ -7140,7 +7139,6 @@ dependencies = [ "zksync_basic_types", "zksync_config", "zksync_protobuf", - "zksync_protobuf_config", "zksync_system_constants", ] @@ -7376,26 +7374,6 @@ dependencies = [ "syn 2.0.89", ] -[[package]] -name = "zksync_protobuf_config" -version = "26.2.0-non-semver-compat" -dependencies = [ - "anyhow", - "hex", - "prost 0.12.6", - "rand", - "secrecy", - "serde_json", - "serde_yaml", - "tracing", - "zksync_basic_types", - "zksync_concurrency", - "zksync_config", - "zksync_protobuf", - "zksync_protobuf_build", - "zksync_types", -] - [[package]] name = "zksync_system_constants" version = "26.2.0-non-semver-compat" diff --git a/zkstack_cli/Cargo.toml b/zkstack_cli/Cargo.toml index cd5e84fc9eb9..5ef8338dca09 100644 --- a/zkstack_cli/Cargo.toml +++ b/zkstack_cli/Cargo.toml @@ -28,7 +28,6 @@ zkstack_cli_git_version_macro = { version = "0.1.2", path = "crates/git_version_ # ZkSync deps zksync_config = { path = "../core/lib/config" } -zksync_protobuf_config = { path = "../core/lib/protobuf_config" } zksync_basic_types = { path = "../core/lib/basic_types" } zksync_system_constants = { path = "../core/lib/constants" } zksync_types = { path = "../core/lib/types" } diff --git a/zkstack_cli/crates/config/Cargo.toml b/zkstack_cli/crates/config/Cargo.toml index 0926f2522cb2..c3d4afdce2c8 100644 --- a/zkstack_cli/crates/config/Cargo.toml +++ b/zkstack_cli/crates/config/Cargo.toml @@ -13,7 +13,6 @@ keywords.workspace = true [dependencies] anyhow.workspace = true clap.workspace = true -zkstack_cli_common.workspace = true ethers.workspace = true rand.workspace = true serde.workspace = true @@ -21,12 +20,13 @@ serde_json.workspace = true serde_yaml.workspace = true strum.workspace = true thiserror.workspace = true -zkstack_cli_types.workspace = true +tokio.workspace = true url.workspace = true xshell.workspace = true -zksync_protobuf_config.workspace = true zksync_protobuf.workspace = true zksync_config.workspace = true zksync_basic_types.workspace = true zksync_system_constants.workspace = true +zkstack_cli_common.workspace = true +zkstack_cli_types.workspace = true diff --git a/zkstack_cli/crates/config/src/chain.rs b/zkstack_cli/crates/config/src/chain.rs index 19f275d78f80..b411b1f882a3 100644 --- a/zkstack_cli/crates/config/src/chain.rs +++ b/zkstack_cli/crates/config/src/chain.rs @@ -7,22 +7,20 @@ use serde::{Deserialize, Serialize, Serializer}; use xshell::Shell; use zkstack_cli_types::{BaseToken, L1BatchCommitmentMode, L1Network, ProverMode, WalletCreation}; use zksync_basic_types::L2ChainId; -use zksync_config::{ - configs::{gateway::GatewayChainConfig, GatewayConfig}, - DAClientConfig::{Avail, NoDA}, -}; +use zksync_config::configs::{gateway::GatewayChainConfig, GatewayConfig}; use crate::{ consts::{ - CONFIG_NAME, CONTRACTS_FILE, EN_CONFIG_FILE, GENERAL_FILE, GENESIS_FILE, + CONFIG_NAME, CONTRACTS_FILE, EN_CONFIG_FILE, GATEWAY_FILE, GENERAL_FILE, GENESIS_FILE, L1_CONTRACTS_FOUNDRY, SECRETS_FILE, WALLETS_FILE, }, create_localhost_wallets, + raw::RawConfig, traits::{ FileConfigWithDefaultName, ReadConfig, ReadConfigWithBasePath, SaveConfig, SaveConfigWithBasePath, ZkStackConfig, }, - ContractsConfig, GeneralConfig, GenesisConfig, SecretsConfig, WalletsConfig, GATEWAY_FILE, + ContractsConfig, WalletsConfig, }; /// Chain configuration file. This file is created in the chain @@ -91,12 +89,12 @@ impl ChainConfig { self.shell.get().expect("Not initialized") } - pub fn get_genesis_config(&self) -> anyhow::Result { - GenesisConfig::read_with_base_path(self.get_shell(), &self.configs) + pub async fn get_genesis_config(&self) -> anyhow::Result { + RawConfig::read(self.get_shell(), self.path_to_genesis_config()).await } - pub fn get_general_config(&self) -> anyhow::Result { - GeneralConfig::read_with_base_path(self.get_shell(), &self.configs) + pub async fn get_general_config(&self) -> anyhow::Result { + RawConfig::read(self.get_shell(), self.path_to_general_config()).await } pub fn get_wallets_config(&self) -> anyhow::Result { @@ -112,25 +110,12 @@ impl ChainConfig { anyhow::bail!("Wallets configs has not been found"); } - pub fn get_da_validator_type(&self) -> anyhow::Result { - let general = self.get_general_config().expect("General config not found"); - match ( - self.l1_batch_commit_data_generator_mode, - general.da_client_config, - ) { - (L1BatchCommitmentMode::Rollup, _) => Ok(DAValidatorType::Rollup), - (L1BatchCommitmentMode::Validium, None | Some(NoDA)) => Ok(DAValidatorType::NoDA), - (L1BatchCommitmentMode::Validium, Some(Avail(_))) => Ok(DAValidatorType::Avail), - _ => anyhow::bail!("DAValidatorType is not supported"), - } - } - pub fn get_contracts_config(&self) -> anyhow::Result { ContractsConfig::read_with_base_path(self.get_shell(), &self.configs) } - pub fn get_secrets_config(&self) -> anyhow::Result { - SecretsConfig::read_with_base_path(self.get_shell(), &self.configs) + pub async fn get_secrets_config(&self) -> anyhow::Result { + RawConfig::read(self.get_shell(), self.path_to_secrets_config()).await } pub fn get_gateway_config(&self) -> anyhow::Result { @@ -165,10 +150,6 @@ impl ChainConfig { self.configs.join(GATEWAY_FILE) } - pub fn save_general_config(&self, general_config: &GeneralConfig) -> anyhow::Result<()> { - general_config.save_with_base_path(self.get_shell(), &self.configs) - } - pub fn path_to_l1_foundry(&self) -> PathBuf { self.link_to_code.join(L1_CONTRACTS_FOUNDRY) } diff --git a/zkstack_cli/crates/config/src/consensus_config.rs b/zkstack_cli/crates/config/src/consensus_config.rs deleted file mode 100644 index 0bb4750d1fc0..000000000000 --- a/zkstack_cli/crates/config/src/consensus_config.rs +++ /dev/null @@ -1,18 +0,0 @@ -use zksync_config::configs::consensus::ConsensusConfig; -use zksync_protobuf_config::encode_yaml_repr; - -use crate::{ - traits::{FileConfigWithDefaultName, SaveConfig}, - CONSENSUS_CONFIG_FILE, -}; - -impl FileConfigWithDefaultName for ConsensusConfig { - const FILE_NAME: &'static str = CONSENSUS_CONFIG_FILE; -} - -impl SaveConfig for ConsensusConfig { - fn save(&self, shell: &xshell::Shell, path: impl AsRef) -> anyhow::Result<()> { - let bytes = encode_yaml_repr::(self)?; - Ok(shell.write_file(path.as_ref(), bytes)?) - } -} diff --git a/zkstack_cli/crates/config/src/consensus_secrets.rs b/zkstack_cli/crates/config/src/consensus_secrets.rs deleted file mode 100644 index da551a452799..000000000000 --- a/zkstack_cli/crates/config/src/consensus_secrets.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::path::Path; - -use xshell::Shell; -use zksync_config::configs::consensus::ConsensusSecrets; -use zksync_protobuf_config::read_yaml_repr; - -use crate::traits::ReadConfig; - -impl ReadConfig for ConsensusSecrets { - fn read(shell: &Shell, path: impl AsRef) -> anyhow::Result { - let path = shell.current_dir().join(path); - read_yaml_repr::(&path, false) - } -} diff --git a/zkstack_cli/crates/config/src/da.rs b/zkstack_cli/crates/config/src/da.rs new file mode 100644 index 000000000000..744613c3f8d8 --- /dev/null +++ b/zkstack_cli/crates/config/src/da.rs @@ -0,0 +1,39 @@ +//! Mirrored types for data availability configs. + +use serde::Serialize; + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct AvailDefaultConfig { + pub api_node_url: String, + pub app_id: u32, + pub finality_state: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct AvailGasRelayConfig { + pub gas_relay_api_url: String, + pub max_retries: usize, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum AvailClientConfig { + FullClient(AvailDefaultConfig), + GasRelay(AvailGasRelayConfig), +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct AvailConfig { + pub bridge_api_url: String, + pub timeout_ms: usize, + #[serde(flatten)] + pub config: AvailClientConfig, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct AvailSecrets { + #[serde(skip_serializing_if = "Option::is_none")] + pub seed_phrase: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub gas_relay_api_key: Option, +} diff --git a/zkstack_cli/crates/config/src/external_node.rs b/zkstack_cli/crates/config/src/external_node.rs deleted file mode 100644 index 7d884d3e2346..000000000000 --- a/zkstack_cli/crates/config/src/external_node.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::path::Path; - -use xshell::Shell; -pub use zksync_config::configs::en_config::ENConfig; -use zksync_protobuf_config::{encode_yaml_repr, read_yaml_repr}; - -use crate::{ - consts::EN_CONFIG_FILE, - traits::{FileConfigWithDefaultName, ReadConfig, SaveConfig}, -}; - -impl FileConfigWithDefaultName for ENConfig { - const FILE_NAME: &'static str = EN_CONFIG_FILE; -} - -impl SaveConfig for ENConfig { - fn save(&self, shell: &Shell, path: impl AsRef) -> anyhow::Result<()> { - let bytes = encode_yaml_repr::(self)?; - Ok(shell.write_file(path, bytes)?) - } -} - -impl ReadConfig for ENConfig { - fn read(shell: &Shell, path: impl AsRef) -> anyhow::Result { - let path = shell.current_dir().join(path); - read_yaml_repr::(&path, false) - } -} diff --git a/zkstack_cli/crates/config/src/forge_interface/deploy_ecosystem/input.rs b/zkstack_cli/crates/config/src/forge_interface/deploy_ecosystem/input.rs index 47fe66143250..c90c3a08de3b 100644 --- a/zkstack_cli/crates/config/src/forge_interface/deploy_ecosystem/input.rs +++ b/zkstack_cli/crates/config/src/forge_interface/deploy_ecosystem/input.rs @@ -7,14 +7,42 @@ use ethers::{ use rand::Rng; use serde::{Deserialize, Serialize}; use zkstack_cli_types::L1Network; -use zksync_basic_types::L2ChainId; +use zksync_basic_types::{protocol_version::ProtocolSemanticVersion, L2ChainId}; use crate::{ consts::INITIAL_DEPLOYMENT_FILE, + raw::RawConfig, traits::{FileConfigWithDefaultName, ZkStackConfig}, - ContractsConfig, GenesisConfig, WalletsConfig, ERC20_DEPLOYMENT_FILE, + ContractsConfig, WalletsConfig, ERC20_DEPLOYMENT_FILE, }; +/// Part of the genesis config influencing `DeployGatewayCTMInput`. +#[derive(Debug)] +pub struct GenesisInput { + pub bootloader_hash: H256, + pub default_aa_hash: H256, + pub evm_emulator_hash: Option, + pub genesis_root_hash: H256, + pub rollup_last_leaf_index: u64, + pub genesis_commitment: H256, + pub protocol_version: ProtocolSemanticVersion, +} + +impl GenesisInput { + // FIXME: is this enough? (cf. aliases in the "real" config definition) + pub fn new(raw: &RawConfig) -> anyhow::Result { + Ok(Self { + bootloader_hash: raw.get("bootloader_hash")?, + default_aa_hash: raw.get("default_aa_hash")?, + evm_emulator_hash: raw.get_opt("evm_emulator_hash")?, + genesis_root_hash: raw.get("genesis_root")?, + rollup_last_leaf_index: raw.get("genesis_rollup_leaf_index")?, + genesis_commitment: raw.get("genesis_batch_commitment")?, + protocol_version: raw.get("genesis_protocol_semantic_version")?, + }) + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct InitialDeploymentConfig { #[serde(skip_serializing_if = "Option::is_none")] @@ -121,7 +149,7 @@ impl ZkStackConfig for DeployL1Config {} impl DeployL1Config { pub fn new( - genesis_config: &GenesisConfig, + genesis_input: &GenesisInput, wallets_config: &WalletsConfig, initial_deployment_config: &InitialDeploymentConfig, era_chain_id: L2ChainId, @@ -149,18 +177,18 @@ impl DeployL1Config { .diamond_init_max_pubdata_per_batch, diamond_init_minimal_l2_gas_price: initial_deployment_config .diamond_init_minimal_l2_gas_price, - bootloader_hash: genesis_config.bootloader_hash.unwrap(), - default_aa_hash: genesis_config.default_aa_hash.unwrap(), - evm_emulator_hash: genesis_config.evm_emulator_hash, + bootloader_hash: genesis_input.bootloader_hash, + default_aa_hash: genesis_input.default_aa_hash, + evm_emulator_hash: genesis_input.evm_emulator_hash, diamond_init_priority_tx_max_pubdata: initial_deployment_config .diamond_init_priority_tx_max_pubdata, diamond_init_pubdata_pricing_mode: initial_deployment_config .diamond_init_pubdata_pricing_mode, // These values are not optional in genesis config with file based configuration - genesis_batch_commitment: genesis_config.genesis_commitment.unwrap(), - genesis_rollup_leaf_index: genesis_config.rollup_last_leaf_index.unwrap(), - genesis_root: genesis_config.genesis_root_hash.unwrap(), - latest_protocol_version: genesis_config.protocol_version.unwrap().pack(), + genesis_batch_commitment: genesis_input.genesis_commitment, + genesis_rollup_leaf_index: genesis_input.rollup_last_leaf_index, + genesis_root: genesis_input.genesis_root_hash, + latest_protocol_version: genesis_input.protocol_version.pack(), recursion_circuits_set_vks_hash: H256::zero(), recursion_leaf_level_vk_hash: H256::zero(), recursion_node_level_vk_hash: H256::zero(), diff --git a/zkstack_cli/crates/config/src/forge_interface/deploy_gateway_ctm/input.rs b/zkstack_cli/crates/config/src/forge_interface/deploy_gateway_ctm/input.rs index afd71cd97757..fdda2009978c 100644 --- a/zkstack_cli/crates/config/src/forge_interface/deploy_gateway_ctm/input.rs +++ b/zkstack_cli/crates/config/src/forge_interface/deploy_gateway_ctm/input.rs @@ -2,10 +2,10 @@ use ethers::abi::Address; use serde::{Deserialize, Serialize}; use zkstack_cli_types::ProverMode; use zksync_basic_types::{H256, U256}; -use zksync_config::GenesisConfig; use crate::{ - forge_interface::deploy_ecosystem::input::InitialDeploymentConfig, traits::ZkStackConfig, + forge_interface::deploy_ecosystem::input::{GenesisInput, InitialDeploymentConfig}, + traits::ZkStackConfig, ChainConfig, ContractsConfig, EcosystemConfig, }; @@ -59,7 +59,7 @@ impl DeployGatewayCTMInput { pub fn new( chain_config: &ChainConfig, ecosystem_config: &EcosystemConfig, - genesis_config: &GenesisConfig, + genesis_input: &GenesisInput, contracts_config: &ContractsConfig, initial_deployment_config: &InitialDeploymentConfig, ) -> Self { @@ -107,16 +107,16 @@ impl DeployGatewayCTMInput { diamond_init_minimal_l2_gas_price: initial_deployment_config .diamond_init_minimal_l2_gas_price, - bootloader_hash: genesis_config.bootloader_hash.unwrap(), - default_aa_hash: genesis_config.default_aa_hash.unwrap(), + bootloader_hash: genesis_input.bootloader_hash, + default_aa_hash: genesis_input.default_aa_hash, priority_tx_max_gas_limit: initial_deployment_config.priority_tx_max_gas_limit, - genesis_root: genesis_config.genesis_root_hash.unwrap(), - genesis_rollup_leaf_index: genesis_config.rollup_last_leaf_index.unwrap(), - genesis_batch_commitment: genesis_config.genesis_commitment.unwrap(), + genesis_root: genesis_input.genesis_root_hash, + genesis_rollup_leaf_index: genesis_input.rollup_last_leaf_index, + genesis_batch_commitment: genesis_input.genesis_commitment, - latest_protocol_version: genesis_config.protocol_version.unwrap().pack(), + latest_protocol_version: genesis_input.protocol_version.pack(), expected_rollup_l2_da_validator: contracts_config .ecosystem_contracts diff --git a/zkstack_cli/crates/config/src/forge_interface/deploy_l2_contracts/input.rs b/zkstack_cli/crates/config/src/forge_interface/deploy_l2_contracts/input.rs index 78ffcd16eaa8..41aa65bf3bfb 100644 --- a/zkstack_cli/crates/config/src/forge_interface/deploy_l2_contracts/input.rs +++ b/zkstack_cli/crates/config/src/forge_interface/deploy_l2_contracts/input.rs @@ -1,8 +1,10 @@ use ethers::types::Address; use serde::{Deserialize, Serialize}; -use zksync_basic_types::{L2ChainId, U256}; +use zksync_basic_types::{commitment::L1BatchCommitmentMode, L2ChainId, U256}; -use crate::{traits::ZkStackConfig, ChainConfig, ContractsConfig}; +use crate::{ + get_da_client_type, traits::ZkStackConfig, ChainConfig, ContractsConfig, DAValidatorType, +}; impl ZkStackConfig for DeployL2ContractsInput {} @@ -21,13 +23,14 @@ pub struct DeployL2ContractsInput { } impl DeployL2ContractsInput { - pub fn new( + pub async fn new( chain_config: &ChainConfig, contracts_config: &ContractsConfig, era_chain_id: L2ChainId, ) -> anyhow::Result { let contracts = chain_config.get_contracts_config()?; let wallets = chain_config.get_wallets_config()?; + let da_validator_type = get_da_validator_type(chain_config).await?; Ok(Self { era_chain_id, @@ -36,8 +39,22 @@ impl DeployL2ContractsInput { bridgehub: contracts.ecosystem_contracts.bridgehub_proxy_addr, governance: contracts_config.l1.governance_addr, erc20_bridge: contracts.bridges.erc20.l1_address, - da_validator_type: U256::from(chain_config.get_da_validator_type()? as u8), + da_validator_type: U256::from(da_validator_type as u8), consensus_registry_owner: wallets.governor.address, }) } } + +async fn get_da_validator_type(config: &ChainConfig) -> anyhow::Result { + let general = config.get_general_config().await?; + + match ( + config.l1_batch_commit_data_generator_mode, + get_da_client_type(&general), + ) { + (L1BatchCommitmentMode::Rollup, _) => Ok(DAValidatorType::Rollup), + (L1BatchCommitmentMode::Validium, None | Some("no_da")) => Ok(DAValidatorType::NoDA), + (L1BatchCommitmentMode::Validium, Some("avail")) => Ok(DAValidatorType::Avail), + _ => anyhow::bail!("DAValidatorType is not supported"), + } +} diff --git a/zkstack_cli/crates/config/src/forge_interface/gateway_chain_upgrade/input.rs b/zkstack_cli/crates/config/src/forge_interface/gateway_chain_upgrade/input.rs index 41100c55a2ae..8b6320e07ffe 100644 --- a/zkstack_cli/crates/config/src/forge_interface/gateway_chain_upgrade/input.rs +++ b/zkstack_cli/crates/config/src/forge_interface/gateway_chain_upgrade/input.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use ethers::types::Address; use serde::{Deserialize, Serialize}; use zkstack_cli_types::L1BatchCommitmentMode; @@ -23,19 +24,22 @@ pub struct GatewayChainUpgradeChain { } impl GatewayChainUpgradeInput { - pub fn new(current_chain_config: &ChainConfig) -> Self { - let contracts_config = current_chain_config.get_contracts_config().unwrap(); + pub async fn new(current_chain_config: &ChainConfig) -> anyhow::Result { + let contracts_config = current_chain_config + .get_contracts_config() + .context("failed loading contracts config")?; let validum = current_chain_config .get_genesis_config() - .unwrap() - .l1_batch_commit_data_generator_mode + .await + .context("failed loading genesis config")? + .get::("l1_batch_commit_data_generator_mode")? == L1BatchCommitmentMode::Validium; - Self { + Ok(Self { owner_address: current_chain_config .get_wallets_config() - .unwrap() + .context("failed loading wallets config")? .governor .address, chain: GatewayChainUpgradeChain { @@ -45,6 +49,6 @@ impl GatewayChainUpgradeInput { // TODO(EVM-860): we assume that all rollup chains want to forever remain this way permanent_rollup: !validum, }, - } + }) } } diff --git a/zkstack_cli/crates/config/src/forge_interface/gateway_ecosystem_upgrade/input.rs b/zkstack_cli/crates/config/src/forge_interface/gateway_ecosystem_upgrade/input.rs index 8bd300f50581..4d739ac476f5 100644 --- a/zkstack_cli/crates/config/src/forge_interface/gateway_ecosystem_upgrade/input.rs +++ b/zkstack_cli/crates/config/src/forge_interface/gateway_ecosystem_upgrade/input.rs @@ -3,8 +3,9 @@ use serde::{Deserialize, Serialize}; use zksync_basic_types::L2ChainId; use crate::{ - forge_interface::deploy_ecosystem::input::InitialDeploymentConfig, traits::ZkStackConfig, - ContractsConfig, GenesisConfig, + forge_interface::deploy_ecosystem::input::{GenesisInput, InitialDeploymentConfig}, + traits::ZkStackConfig, + ContractsConfig, }; #[derive(Debug, Deserialize, Serialize, Clone)] @@ -21,7 +22,7 @@ impl ZkStackConfig for GatewayEcosystemUpgradeInput {} impl GatewayEcosystemUpgradeInput { pub fn new( - new_genesis_config: &GenesisConfig, + new_genesis_input: &GenesisInput, current_contracts_config: &ContractsConfig, // It is expected to not change between the versions initial_deployment_config: &InitialDeploymentConfig, @@ -48,16 +49,16 @@ impl GatewayEcosystemUpgradeInput { .diamond_init_max_pubdata_per_batch, diamond_init_minimal_l2_gas_price: initial_deployment_config .diamond_init_minimal_l2_gas_price, - bootloader_hash: new_genesis_config.bootloader_hash.unwrap(), - default_aa_hash: new_genesis_config.default_aa_hash.unwrap(), + bootloader_hash: new_genesis_input.bootloader_hash, + default_aa_hash: new_genesis_input.default_aa_hash, diamond_init_priority_tx_max_pubdata: initial_deployment_config .diamond_init_priority_tx_max_pubdata, diamond_init_pubdata_pricing_mode: initial_deployment_config .diamond_init_pubdata_pricing_mode, // These values are not optional in genesis config with file based configuration - genesis_batch_commitment: new_genesis_config.genesis_commitment.unwrap(), - genesis_rollup_leaf_index: new_genesis_config.rollup_last_leaf_index.unwrap(), - genesis_root: new_genesis_config.genesis_root_hash.unwrap(), + genesis_batch_commitment: new_genesis_input.genesis_commitment, + genesis_rollup_leaf_index: new_genesis_input.rollup_last_leaf_index, + genesis_root: new_genesis_input.genesis_root_hash, recursion_circuits_set_vks_hash: H256::zero(), recursion_leaf_level_vk_hash: H256::zero(), recursion_node_level_vk_hash: H256::zero(), diff --git a/zkstack_cli/crates/config/src/general.rs b/zkstack_cli/crates/config/src/general.rs index c1639d6bea15..9eb7dc7756cd 100644 --- a/zkstack_cli/crates/config/src/general.rs +++ b/zkstack_cli/crates/config/src/general.rs @@ -1,16 +1,10 @@ use std::path::{Path, PathBuf}; -use anyhow::Context; -use url::Url; use xshell::Shell; use zkstack_cli_common::yaml::merge_yaml; -use zksync_config::configs::object_store::ObjectStoreMode; -pub use zksync_config::configs::GeneralConfig; -use zksync_protobuf_config::{encode_yaml_repr, read_yaml_repr}; use crate::{ - consts::GENERAL_FILE, - traits::{ConfigWithL2RpcUrl, FileConfigWithDefaultName, ReadConfig, SaveConfig}, + raw::{PatchedConfig, RawConfig}, ChainConfig, }; @@ -40,77 +34,57 @@ impl FileArtifacts { } } -pub fn set_rocks_db_config(config: &mut GeneralConfig, rocks_dbs: RocksDbs) -> anyhow::Result<()> { - config - .db_config - .as_mut() - .context("DB config is not presented")? - .state_keeper_db_path = rocks_dbs.state_keeper.to_str().unwrap().to_string(); - config - .db_config - .as_mut() - .context("DB config is not presented")? - .merkle_tree - .path = rocks_dbs.merkle_tree.to_str().unwrap().to_string(); - config - .protective_reads_writer_config - .as_mut() - .context("Protective reads config is not presented")? - .db_path = rocks_dbs.protective_reads.to_str().unwrap().to_string(); - config - .basic_witness_input_producer_config - .as_mut() - .context("Basic witness input producer config is not presented")? - .db_path = rocks_dbs - .basic_witness_input_producer - .to_str() - .unwrap() - .to_string(); +pub fn set_rocks_db_config(config: &mut PatchedConfig, rocks_dbs: RocksDbs) -> anyhow::Result<()> { + config.insert_path("db.state_keeper_db_path", &rocks_dbs.state_keeper)?; + config.insert_path("db.merkle_tree.path", &rocks_dbs.merkle_tree)?; + config.insert_path( + "protective_reads_writer.db_path", + &rocks_dbs.protective_reads, + )?; + config.insert_path( + "basic_witness_input_producer.db_path", + &rocks_dbs.basic_witness_input_producer, + )?; Ok(()) } -pub fn set_file_artifacts(config: &mut GeneralConfig, file_artifacts: FileArtifacts) { - macro_rules! set_artifact_path { - ($config:expr, $name:ident, $value:expr) => { - $config - .as_mut() - .map(|a| set_artifact_path!(a.$name, $value)) - }; +pub fn set_file_artifacts( + config: &mut PatchedConfig, + file_artifacts: FileArtifacts, +) -> anyhow::Result<()> { + set_file_backed_path_if_selected( + config, + "prover.prover_object_store", + &file_artifacts.prover_object_store, + )?; + set_file_backed_path_if_selected( + config, + "prover.public_object_store", + &file_artifacts.public_object_store, + )?; + set_file_backed_path_if_selected( + config, + "snapshot_creator.object_store", + &file_artifacts.snapshot, + )?; + set_file_backed_path_if_selected( + config, + "snapshot_recovery.object_store", + &file_artifacts.snapshot, + )?; + Ok(()) +} - ($config:expr, $value:expr) => { - $config.as_mut().map(|a| { - if let ObjectStoreMode::FileBacked { - ref mut file_backed_base_path, - } = &mut a.mode - { - *file_backed_base_path = $value.to_str().unwrap().to_string() - } - }) - }; +fn set_file_backed_path_if_selected( + config: &mut PatchedConfig, + prefix: &str, + path: &Path, +) -> anyhow::Result<()> { + let container = config.base().get_raw(&format!("{prefix}.file_backed")); + if matches!(container, Some(serde_yaml::Value::Mapping(_))) { + config.insert_path(&format!("{prefix}.file_backed.file_backed_base_path"), path)?; } - - set_artifact_path!( - config.prover_config, - prover_object_store, - file_artifacts.prover_object_store - ); - set_artifact_path!( - config.prover_config, - public_object_store, - file_artifacts.public_object_store - ); - set_artifact_path!( - config.snapshot_creator, - object_store, - file_artifacts.snapshot - ); - set_artifact_path!( - config.snapshot_recovery, - object_store, - file_artifacts.snapshot - ); - - set_artifact_path!(config.core_object_store, file_artifacts.core_object_store); + Ok(()) } pub fn override_config(shell: &Shell, path: PathBuf, chain: &ChainConfig) -> anyhow::Result<()> { @@ -122,32 +96,13 @@ pub fn override_config(shell: &Shell, path: PathBuf, chain: &ChainConfig) -> any Ok(()) } -impl FileConfigWithDefaultName for GeneralConfig { - const FILE_NAME: &'static str = GENERAL_FILE; -} - -impl SaveConfig for GeneralConfig { - fn save(&self, shell: &Shell, path: impl AsRef) -> anyhow::Result<()> { - let bytes = - encode_yaml_repr::(self)?; - Ok(shell.write_file(path, bytes)?) - } -} - -impl ReadConfig for GeneralConfig { - fn read(shell: &Shell, path: impl AsRef) -> anyhow::Result { - let path = shell.current_dir().join(path); - read_yaml_repr::(&path, false) - } -} - -impl ConfigWithL2RpcUrl for GeneralConfig { - fn get_l2_rpc_url(&self) -> anyhow::Result { - self.api_config - .as_ref() - .map(|api_config| &api_config.web3_json_rpc.http_url) - .context("API config is missing")? - .parse() - .context("Failed to parse L2 RPC URL") - } +pub fn get_da_client_type(general: &RawConfig) -> Option<&str> { + general.get_raw("da_client").and_then(|val| { + let val = val.as_mapping()?; + if val.len() == 1 { + val.keys().next()?.as_str() + } else { + None + } + }) } diff --git a/zkstack_cli/crates/config/src/genesis.rs b/zkstack_cli/crates/config/src/genesis.rs index e457f3d1924c..0f75f374cbf9 100644 --- a/zkstack_cli/crates/config/src/genesis.rs +++ b/zkstack_cli/crates/config/src/genesis.rs @@ -1,41 +1,15 @@ -use std::path::Path; - -use xshell::Shell; -use zksync_basic_types::L1ChainId; -pub use zksync_config::GenesisConfig; -use zksync_protobuf_config::{encode_yaml_repr, read_yaml_repr}; - -use crate::{ - consts::GENESIS_FILE, - traits::{FileConfigWithDefaultName, ReadConfig, SaveConfig}, - ChainConfig, -}; +use crate::{raw::PatchedConfig, ChainConfig}; pub fn update_from_chain_config( - genesis: &mut GenesisConfig, + genesis: &mut PatchedConfig, config: &ChainConfig, ) -> anyhow::Result<()> { - genesis.l2_chain_id = config.chain_id; + genesis.insert("l2_chain_id", config.chain_id.as_u64())?; // TODO(EVM-676): for now, the settlement layer is always the same as the L1 network - genesis.l1_chain_id = L1ChainId(config.l1_network.chain_id()); - genesis.l1_batch_commit_data_generator_mode = config.l1_batch_commit_data_generator_mode; + genesis.insert("l1_chain_id", config.l1_network.chain_id())?; + genesis.insert_yaml( + "l1_batch_commit_data_generator_mode", + config.l1_batch_commit_data_generator_mode, + )?; Ok(()) } - -impl FileConfigWithDefaultName for GenesisConfig { - const FILE_NAME: &'static str = GENESIS_FILE; -} - -impl SaveConfig for GenesisConfig { - fn save(&self, shell: &Shell, path: impl AsRef) -> anyhow::Result<()> { - let bytes = encode_yaml_repr::(self)?; - Ok(shell.write_file(path, bytes)?) - } -} - -impl ReadConfig for GenesisConfig { - fn read(shell: &Shell, path: impl AsRef) -> anyhow::Result { - let path = shell.current_dir().join(path); - read_yaml_repr::(&path, false) - } -} diff --git a/zkstack_cli/crates/config/src/lib.rs b/zkstack_cli/crates/config/src/lib.rs index 4d4fb8da61d2..f4f1e3a68835 100644 --- a/zkstack_cli/crates/config/src/lib.rs +++ b/zkstack_cli/crates/config/src/lib.rs @@ -10,28 +10,25 @@ pub use manipulations::*; pub use secrets::*; pub use wallet_creation::*; pub use wallets::*; -pub use zksync_protobuf_config::{encode_yaml_repr, read_yaml_repr}; mod apps; mod chain; mod consts; mod contracts; +pub mod da; +pub mod docker_compose; mod ecosystem; +pub mod explorer; +pub mod explorer_compose; mod file_config; +pub mod forge_interface; mod gateway; mod general; mod genesis; mod manipulations; +pub mod portal; +pub mod raw; mod secrets; +pub mod traits; mod wallet_creation; mod wallets; - -pub mod consensus_config; -pub mod consensus_secrets; -pub mod docker_compose; -pub mod explorer; -pub mod explorer_compose; -pub mod external_node; -pub mod forge_interface; -pub mod portal; -pub mod traits; diff --git a/zkstack_cli/crates/config/src/raw.rs b/zkstack_cli/crates/config/src/raw.rs new file mode 100644 index 000000000000..b14f404422f8 --- /dev/null +++ b/zkstack_cli/crates/config/src/raw.rs @@ -0,0 +1,156 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Context; +use serde::{de::DeserializeOwned, Serialize}; +use tokio::fs; +use xshell::Shell; + +#[derive(Debug)] +pub struct RawConfig { + path: PathBuf, + inner: serde_yaml::Value, +} + +impl RawConfig { + pub async fn read(shell: &Shell, path: PathBuf) -> anyhow::Result { + let path = shell.current_dir().join(&path); + let raw = fs::read_to_string(&path) + .await + .with_context(|| format!("failed reading config at `{path:?}`"))?; + let inner: serde_yaml::Value = serde_yaml::from_str(&raw) + .with_context(|| format!("failed deserializing config at `{path:?}` as YAML"))?; + anyhow::ensure!( + matches!(&inner, serde_yaml::Value::Mapping(_)), + "configuration is not a map" + ); + Ok(Self { inner, path }) + } + + pub fn get_raw(&self, path: &str) -> Option<&serde_yaml::Value> { + path.split('.') + .try_fold(&self.inner, |ptr, segment| match ptr { + serde_yaml::Value::Mapping(map) => map.get(segment), + _ => None, + }) + } + + pub fn get_opt(&self, path: &str) -> anyhow::Result> { + let Some(raw) = self.get_raw(path) else { + return Ok(None); + }; + serde_yaml::from_value(raw.clone()).with_context(|| { + format!( + "failed deserializing config param `{path}` in `{:?}`", + self.path + ) + }) + } + + pub fn get(&self, path: &str) -> anyhow::Result { + self.get_opt(path)? + .with_context(|| format!("config param `{path}` is missing in {:?}", self.path)) + } + + pub fn patched(self) -> PatchedConfig { + PatchedConfig { base: self } + } +} + +/// Mutable YAML configuration file. +#[derive(Debug)] +#[must_use = "Must be persisted"] +pub struct PatchedConfig { + base: RawConfig, +} + +impl PatchedConfig { + pub fn empty(shell: &Shell, path: PathBuf) -> Self { + let path = shell.current_dir().join(&path); + Self { + base: RawConfig { + path, + inner: serde_yaml::Value::Mapping(serde_yaml::Mapping::default()), + }, + } + } + + pub fn base(&self) -> &RawConfig { + &self.base + } + + pub fn insert(&mut self, key: &str, value: impl Into) -> anyhow::Result<()> { + assert!(!key.is_empty(), "key cannot be empty"); + let value = value.into(); + + let serde_yaml::Value::Mapping(map) = &mut self.base.inner else { + unreachable!(); // checked during initialization + }; + let mut map = map; + if let Some((parent_path, last_segment)) = key.rsplit_once('.') { + for segment in parent_path.split('.') { + if !map.contains_key(segment) { + let new_map = serde_yaml::Mapping::new(); + map.insert(segment.into(), new_map.into()); + } + + map = match map.get_mut(segment) { + Some(serde_yaml::Value::Mapping(child)) => child, + Some(_) => anyhow::bail!("Encountered non-map parent when inserting `{key}`"), + None => unreachable!(), + }; + } + map.insert(last_segment.into(), value); + } else { + map.insert(key.into(), value); + } + Ok(()) + } + + pub fn insert_yaml(&mut self, key: &str, value: impl Serialize) -> anyhow::Result<()> { + let value = serde_yaml::to_value(value) + .unwrap_or_else(|err| panic!("failed serializing config value at `{key}`: {err}")); + self.insert(key, value) + } + + pub fn insert_path(&mut self, key: &str, value: &Path) -> anyhow::Result<()> { + let value = value + .to_str() + .with_context(|| format!("path at `{key}` is not UTF-8"))?; + self.insert(key, value)?; + Ok(()) + } + + pub fn extend(&mut self, source: serde_yaml::Mapping) { + let serde_yaml::Value::Mapping(map) = &mut self.base.inner else { + unreachable!(); // checked during initialization + }; + map.extend(source); + } + + pub fn remove(&mut self, key: &str) { + let serde_yaml::Value::Mapping(map) = &mut self.base.inner else { + unreachable!(); // checked during initialization + }; + let mut map = map; + + if let Some((parent_path, last_segment)) = key.rsplit_once('.') { + for segment in parent_path.split('.') { + map = match map.get_mut(segment) { + Some(serde_yaml::Value::Mapping(child)) => child, + _ => return, + }; + } + map.remove(last_segment); + } else { + map.remove(key); + } + } + + pub async fn save(self) -> anyhow::Result<()> { + let contents = + serde_yaml::to_string(&self.base.inner).context("failed serializing config")?; + fs::write(&self.base.path, contents) + .await + .with_context(|| format!("failed writing config to `{:?}`", self.base.path)) + } +} diff --git a/zkstack_cli/crates/config/src/secrets.rs b/zkstack_cli/crates/config/src/secrets.rs index 91e8964b4651..fd33ebfd86d8 100644 --- a/zkstack_cli/crates/config/src/secrets.rs +++ b/zkstack_cli/crates/config/src/secrets.rs @@ -1,64 +1,27 @@ -use std::{path::Path, str::FromStr}; - -use anyhow::Context; -use xshell::Shell; use zkstack_cli_common::db::DatabaseConfig; -use zksync_basic_types::url::SensitiveUrl; -pub use zksync_config::configs::Secrets as SecretsConfig; -use zksync_protobuf_config::{encode_yaml_repr, read_yaml_repr}; -use crate::{ - consts::SECRETS_FILE, - traits::{FileConfigWithDefaultName, ReadConfig, SaveConfig}, -}; +use crate::raw::PatchedConfig; pub fn set_server_database( - secrets: &mut SecretsConfig, + secrets: &mut PatchedConfig, server_db_config: &DatabaseConfig, ) -> anyhow::Result<()> { - let database = secrets - .database - .as_mut() - .context("Server database must be presented")?; - database.server_url = Some(SensitiveUrl::from(server_db_config.full_url())); - Ok(()) + secrets.insert( + "database.server_url", + server_db_config.full_url().to_string(), + ) } pub fn set_prover_database( - secrets: &mut SecretsConfig, + secrets: &mut PatchedConfig, prover_db_config: &DatabaseConfig, ) -> anyhow::Result<()> { - let database = secrets - .database - .as_mut() - .context("Prover database must be presented")?; - database.prover_url = Some(SensitiveUrl::from(prover_db_config.full_url())); - Ok(()) -} - -pub fn set_l1_rpc_url(secrets: &mut SecretsConfig, l1_rpc_url: String) -> anyhow::Result<()> { - secrets - .l1 - .as_mut() - .context("L1 Secrets must be presented")? - .l1_rpc_url = SensitiveUrl::from_str(&l1_rpc_url)?; - Ok(()) -} - -impl FileConfigWithDefaultName for SecretsConfig { - const FILE_NAME: &'static str = SECRETS_FILE; -} - -impl SaveConfig for SecretsConfig { - fn save(&self, shell: &Shell, path: impl AsRef) -> anyhow::Result<()> { - let bytes = encode_yaml_repr::(self)?; - Ok(shell.write_file(path, bytes)?) - } + secrets.insert( + "database.prover_url", + prover_db_config.full_url().to_string(), + ) } -impl ReadConfig for SecretsConfig { - fn read(shell: &Shell, path: impl AsRef) -> anyhow::Result { - let path = shell.current_dir().join(path); - read_yaml_repr::(&path, false) - } +pub fn set_l1_rpc_url(secrets: &mut PatchedConfig, l1_rpc_url: String) -> anyhow::Result<()> { + secrets.insert("l1.l1_rpc_url", l1_rpc_url) } diff --git a/zkstack_cli/crates/config/src/traits.rs b/zkstack_cli/crates/config/src/traits.rs index d21641e33ff5..e5429cc683c6 100644 --- a/zkstack_cli/crates/config/src/traits.rs +++ b/zkstack_cli/crates/config/src/traits.rs @@ -2,7 +2,6 @@ use std::path::{Path, PathBuf}; use anyhow::{bail, Context}; use serde::{de::DeserializeOwned, Serialize}; -use url::Url; use xshell::Shell; use zkstack_cli_common::files::{ read_json_file, read_toml_file, read_yaml_file, save_json_file, save_toml_file, save_yaml_file, @@ -157,7 +156,3 @@ fn save_with_comment( } Ok(()) } - -pub trait ConfigWithL2RpcUrl { - fn get_l2_rpc_url(&self) -> anyhow::Result; -} diff --git a/zkstack_cli/crates/zkstack/Cargo.toml b/zkstack_cli/crates/zkstack/Cargo.toml index 169fe593ba14..f39b5f5ea085 100644 --- a/zkstack_cli/crates/zkstack/Cargo.toml +++ b/zkstack_cli/crates/zkstack/Cargo.toml @@ -42,7 +42,6 @@ zksync_config.workspace = true zksync_consensus_roles.workspace = true zksync_consensus_crypto.workspace = true zksync_protobuf.workspace = true -zksync_protobuf_config.workspace = true zksync_types.workspace = true zksync_web3_decl.workspace = true zksync_system_constants.workspace = true @@ -61,9 +60,8 @@ clap_complete.workspace = true dirs.workspace = true ethers.workspace = true xshell.workspace = true -zksync_protobuf_build.workspace = true [features] # Features that allows gateway-chain related actions. -# These should be available for outside users until stabilized. +# These should be available for outside users until stabilized. gateway = [] diff --git a/zkstack_cli/crates/zkstack/build.rs b/zkstack_cli/crates/zkstack/build.rs index d2d478f80227..5d077b77fc82 100644 --- a/zkstack_cli/crates/zkstack/build.rs +++ b/zkstack_cli/crates/zkstack/build.rs @@ -25,15 +25,6 @@ fn main() -> anyhow::Result<()> { println!("cargo:error={}", e); }; - zksync_protobuf_build::Config { - input_root: "src/commands/consensus/proto".into(), - proto_root: "zksync/toolbox/consensus".into(), - dependencies: vec!["::zksync_protobuf_config::proto".parse().unwrap()], - protobuf_crate: "::zksync_protobuf".parse().unwrap(), - is_public: false, - } - .generate() - .unwrap(); Ok(()) } diff --git a/zkstack_cli/crates/zkstack/completion/_zkstack.zsh b/zkstack_cli/crates/zkstack/completion/_zkstack.zsh index c7357d661e2e..93496c220e15 100644 --- a/zkstack_cli/crates/zkstack/completion/_zkstack.zsh +++ b/zkstack_cli/crates/zkstack/completion/_zkstack.zsh @@ -1906,7 +1906,7 @@ _arguments "${_arguments_options[@]}" : \ '(--clone)--bellman-cuda-dir=[]:BELLMAN_CUDA_DIR:_default' \ '--bellman-cuda=[]' \ '--setup-compressor-key=[]' \ -'--path=[]:PATH:_default' \ +'--path=[]:PATH:_files' \ '--region=[]:REGION:(us europe asia)' \ '--mode=[]:MODE:(download generate)' \ '--setup-keys=[]' \ @@ -1977,7 +1977,7 @@ _arguments "${_arguments_options[@]}" : \ ;; (compressor-keys) _arguments "${_arguments_options[@]}" : \ -'--path=[]:PATH:_default' \ +'--path=[]:PATH:_files' \ '--chain=[Chain to use]:CHAIN:_default' \ '-v[Verbose mode]' \ '--verbose[Verbose mode]' \ diff --git a/zkstack_cli/crates/zkstack/completion/zkstack.fish b/zkstack_cli/crates/zkstack/completion/zkstack.fish index b724ddc6c723..b05971fe5f3f 100644 --- a/zkstack_cli/crates/zkstack/completion/zkstack.fish +++ b/zkstack_cli/crates/zkstack/completion/zkstack.fish @@ -602,7 +602,7 @@ complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_ false\t''" complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init" -l setup-compressor-key -r -f -a "true\t'' false\t''" -complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init" -l path -r +complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init" -l path -r -F complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init" -l region -r -f -a "us\t'' europe\t'' asia\t''" @@ -668,7 +668,7 @@ complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_ complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init-bellman-cuda" -s v -l verbose -d 'Verbose mode' complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init-bellman-cuda" -l ignore-prerequisites -d 'Ignores prerequisites checks' complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from init-bellman-cuda" -s h -l help -d 'Print help' -complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from compressor-keys" -l path -r +complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from compressor-keys" -l path -r -F complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from compressor-keys" -l chain -d 'Chain to use' -r complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from compressor-keys" -s v -l verbose -d 'Verbose mode' complete -c zkstack -n "__fish_zkstack_using_subcommand prover; and __fish_seen_subcommand_from compressor-keys" -l ignore-prerequisites -d 'Ignores prerequisites checks' diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/accept_chain_ownership.rs b/zkstack_cli/crates/zkstack/src/commands/chain/accept_chain_ownership.rs index 46b92248ae3f..b238c1160f66 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/accept_chain_ownership.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/accept_chain_ownership.rs @@ -7,7 +7,6 @@ use crate::{ accept_ownership::accept_admin, messages::{ MSG_ACCEPTING_ADMIN_SPINNER, MSG_CHAIN_NOT_INITIALIZED, MSG_CHAIN_OWNERSHIP_TRANSFERRED, - MSG_L1_SECRETS_MUST_BE_PRESENTED, }, }; @@ -17,13 +16,8 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { .load_current_chain() .context(MSG_CHAIN_NOT_INITIALIZED)?; let contracts = chain_config.get_contracts_config()?; - let secrets = chain_config.get_secrets_config()?; - let l1_rpc_url = secrets - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + let secrets = chain_config.get_secrets_config().await?; + let l1_rpc_url = secrets.get("l1.l1_rpc_url")?; let spinner = Spinner::new(MSG_ACCEPTING_ADMIN_SPINNER); accept_admin( @@ -33,7 +27,7 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { &chain_config.get_wallets_config()?.governor, contracts.l1.diamond_proxy_addr, &args, - l1_rpc_url.clone(), + l1_rpc_url, ) .await?; spinner.finish(); diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/args/genesis.rs b/zkstack_cli/crates/zkstack/src/commands/chain/args/genesis.rs index ef98a777352e..08421fcc6abf 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/args/genesis.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/args/genesis.rs @@ -56,18 +56,16 @@ impl GenesisArgs { } } - pub fn fill_values_with_secrets( + pub async fn fill_values_with_secrets( mut self, chain_config: &ChainConfig, ) -> anyhow::Result { - let secrets = chain_config.get_secrets_config()?; - let database = secrets - .database - .context("Database secrets must be present")?; + let secrets = chain_config.get_secrets_config().await?; + let server_url = secrets.get_opt::("database.server_url")?; - let (server_db_url, server_db_name) = if let Some(db_full_url) = database.server_url { - let db_config = DatabaseConfig::from_url(db_full_url.expose_url()) - .context("Invalid server database URL")?; + let (server_db_url, server_db_name) = if let Some(db_full_url) = &server_url { + let db_config = + DatabaseConfig::from_url(db_full_url).context("Invalid server database URL")?; (Some(db_config.url), Some(db_config.name)) } else { (None, None) diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/args/init/da_configs.rs b/zkstack_cli/crates/zkstack/src/commands/chain/args/init/da_configs.rs index 4d0e97e7ef05..7a8e6d5f838e 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/args/init/da_configs.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/args/init/da_configs.rs @@ -3,11 +3,8 @@ use serde::{Deserialize, Serialize}; use strum::{Display, EnumIter, IntoEnumIterator}; use url::Url; use zkstack_cli_common::{Prompt, PromptSelect}; -use zksync_config::{ - configs::da_client::avail::{ - AvailClientConfig, AvailDefaultConfig, AvailGasRelayConfig, AvailSecrets, - }, - AvailConfig, +use zkstack_cli_config::da::{ + AvailClientConfig, AvailConfig, AvailDefaultConfig, AvailGasRelayConfig, AvailSecrets, }; use crate::{ diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/build_transactions.rs b/zkstack_cli/crates/zkstack/src/commands/chain/build_transactions.rs index 3bf4db7188f7..43ea4b55ab37 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/build_transactions.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/build_transactions.rs @@ -40,8 +40,9 @@ pub(crate) async fn run(args: BuildTransactionsArgs, shell: &Shell) -> anyhow::R logger::note(MSG_SELECTED_CONFIG, logger::object_to_string(&chain_config)); - let mut genesis_config = chain_config.get_genesis_config()?; + let mut genesis_config = chain_config.get_genesis_config().await?.patched(); update_from_chain_config(&mut genesis_config, &chain_config)?; + // FIXME: config isn't saved; why? // Copy ecosystem contracts let mut contracts_config = config diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/convert_to_gateway.rs b/zkstack_cli/crates/zkstack/src/commands/chain/convert_to_gateway.rs index 0b06cd8de3c2..cb4c5c8e555d 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/convert_to_gateway.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/convert_to_gateway.rs @@ -9,19 +9,19 @@ use zkstack_cli_common::{ }; use zkstack_cli_config::{ forge_interface::{ - deploy_ecosystem::input::InitialDeploymentConfig, + deploy_ecosystem::input::{GenesisInput, InitialDeploymentConfig}, deploy_gateway_ctm::{input::DeployGatewayCTMInput, output::DeployGatewayCTMOutput}, gateway_preparation::{input::GatewayPreparationConfig, output::GatewayPreparationOutput}, script_params::{DEPLOY_GATEWAY_CTM, GATEWAY_GOVERNANCE_TX_PATH1, GATEWAY_PREPARATION}, }, traits::{ReadConfig, SaveConfig, SaveConfigWithBasePath}, - ChainConfig, EcosystemConfig, GenesisConfig, + ChainConfig, EcosystemConfig, }; use zksync_basic_types::H256; use zksync_config::configs::GatewayConfig; use crate::{ - messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + messages::MSG_CHAIN_NOT_INITIALIZED, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -55,14 +55,12 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { .load_chain(chain_name) .context(MSG_CHAIN_NOT_INITIALIZED)?; let l1_url = chain_config - .get_secrets_config()? - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + .get_secrets_config() + .await? + .get::("l1.l1_rpc_url")?; let mut chain_contracts_config = chain_config.get_contracts_config()?; - let chain_genesis_config = chain_config.get_genesis_config()?; + let chain_genesis_config = chain_config.get_genesis_config().await?; + let genesis_input = GenesisInput::new(&chain_genesis_config)?; // Firstly, deploying gateway contracts let gateway_config = calculate_gateway_ctm( @@ -70,7 +68,7 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { args.clone(), &ecosystem_config, &chain_config, - &chain_genesis_config, + &genesis_input, &ecosystem_config.get_initial_deployment_config().unwrap(), l1_url.clone(), ) @@ -147,7 +145,7 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { args, &ecosystem_config, &chain_config, - &chain_genesis_config, + &genesis_input, &ecosystem_config.get_initial_deployment_config().unwrap(), l1_url, ) @@ -163,8 +161,8 @@ pub async fn calculate_gateway_ctm( forge_args: ForgeScriptArgs, config: &EcosystemConfig, chain_config: &ChainConfig, - chain_genesis_config: &GenesisConfig, - initial_deployemnt_config: &InitialDeploymentConfig, + genesis_input: &GenesisInput, + initial_deployment_config: &InitialDeploymentConfig, l1_rpc_url: String, ) -> anyhow::Result { let contracts_config = chain_config.get_contracts_config()?; @@ -173,9 +171,9 @@ pub async fn calculate_gateway_ctm( let deploy_config = DeployGatewayCTMInput::new( chain_config, config, - chain_genesis_config, + genesis_input, &contracts_config, - initial_deployemnt_config, + initial_deployment_config, ); deploy_config.save(shell, deploy_config_path)?; @@ -214,20 +212,19 @@ pub async fn deploy_gateway_ctm( forge_args: ForgeScriptArgs, config: &EcosystemConfig, chain_config: &ChainConfig, - chain_genesis_config: &GenesisConfig, - initial_deployemnt_config: &InitialDeploymentConfig, + genesis_input: &GenesisInput, + initial_deployment_config: &InitialDeploymentConfig, l1_rpc_url: String, ) -> anyhow::Result<()> { let contracts_config = chain_config.get_contracts_config()?; - // let contracts_config = config.get_contracts_config()?; let deploy_config_path = DEPLOY_GATEWAY_CTM.input(&config.link_to_code); let deploy_config = DeployGatewayCTMInput::new( chain_config, config, - chain_genesis_config, + genesis_input, &contracts_config, - initial_deployemnt_config, + initial_deployment_config, ); deploy_config.save(shell, deploy_config_path)?; diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/create.rs b/zkstack_cli/crates/zkstack/src/commands/chain/create.rs index 529d861a2559..1112907374a4 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/create.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/create.rs @@ -4,11 +4,11 @@ use anyhow::Context; use xshell::Shell; use zkstack_cli_common::{logger, spinner::Spinner}; use zkstack_cli_config::{ - create_local_configs_dir, create_wallets, - traits::{ReadConfigWithBasePath, SaveConfigWithBasePath}, - ChainConfig, EcosystemConfig, GenesisConfig, + create_local_configs_dir, create_wallets, raw::RawConfig, traits::SaveConfigWithBasePath, + ChainConfig, EcosystemConfig, GENESIS_FILE, }; use zksync_basic_types::L2ChainId; +use zksync_types::H256; use crate::{ commands::chain::args::create::{ChainCreateArgs, ChainCreateArgsFinal}, @@ -20,12 +20,12 @@ use crate::{ utils::link_to_code::resolve_link_to_code, }; -pub fn run(args: ChainCreateArgs, shell: &Shell) -> anyhow::Result<()> { +pub async fn run(args: ChainCreateArgs, shell: &Shell) -> anyhow::Result<()> { let mut ecosystem_config = EcosystemConfig::from_file(shell)?; - create(args, &mut ecosystem_config, shell) + create(args, &mut ecosystem_config, shell).await } -pub fn create( +pub async fn create( args: ChainCreateArgs, ecosystem_config: &mut EcosystemConfig, shell: &Shell, @@ -46,7 +46,7 @@ pub fn create( let spinner = Spinner::new(MSG_CREATING_CHAIN_CONFIGURATIONS_SPINNER); let name = args.chain_name.clone(); let set_as_default = args.set_as_default; - create_chain_inner(args, ecosystem_config, shell)?; + create_chain_inner(args, ecosystem_config, shell).await?; if set_as_default { ecosystem_config.default_chain = name; ecosystem_config.save_with_base_path(shell, ".")?; @@ -58,7 +58,7 @@ pub fn create( Ok(()) } -pub(crate) fn create_chain_inner( +pub(crate) async fn create_chain_inner( args: ChainCreateArgsFinal, ecosystem_config: &EcosystemConfig, shell: &Shell, @@ -82,11 +82,12 @@ pub(crate) fn create_chain_inner( args.link_to_code.clone(), args.update_submodules, )?; - let default_genesis_config = GenesisConfig::read_with_base_path( - shell, - EcosystemConfig::default_configs_path(&link_to_code), - )?; - let has_evm_emulation_support = default_genesis_config.evm_emulator_hash.is_some(); + let genesis_config_path = + EcosystemConfig::default_configs_path(&link_to_code).join(GENESIS_FILE); + let default_genesis_config = RawConfig::read(shell, genesis_config_path).await?; + let has_evm_emulation_support = default_genesis_config + .get_opt::("evm_emulator_hash")? + .is_some(); if args.evm_emulator && !has_evm_emulation_support { anyhow::bail!(MSG_EVM_EMULATOR_HASH_MISSING_ERR); } diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/deploy_l2_contracts.rs b/zkstack_cli/crates/zkstack/src/commands/chain/deploy_l2_contracts.rs index 7cf628b1170b..98130859b7a3 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/deploy_l2_contracts.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/deploy_l2_contracts.rs @@ -23,10 +23,7 @@ use zkstack_cli_config::{ }; use crate::{ - messages::{ - MSG_CHAIN_NOT_INITIALIZED, MSG_DEPLOYING_L2_CONTRACT_SPINNER, - MSG_L1_SECRETS_MUST_BE_PRESENTED, - }, + messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_DEPLOYING_L2_CONTRACT_SPINNER}, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -263,9 +260,11 @@ async fn call_forge( chain_config, &ecosystem_config.get_contracts_config()?, ecosystem_config.era_chain_id, - )?; + ) + .await?; + let foundry_contracts_path = chain_config.path_to_l1_foundry(); - let secrets = chain_config.get_secrets_config()?; + let secrets = chain_config.get_secrets_config().await?; input.save( shell, DEPLOY_L2_CONTRACTS_SCRIPT_PARAMS.input(&chain_config.link_to_code), @@ -277,14 +276,7 @@ async fn call_forge( forge_args.clone(), ) .with_ffi() - .with_rpc_url( - secrets - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(), - ); + .with_rpc_url(secrets.get("l1.l1_rpc_url")?); if with_broadcast { forge = forge.with_broadcast(); } diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/deploy_paymaster.rs b/zkstack_cli/crates/zkstack/src/commands/chain/deploy_paymaster.rs index 1c103ea29910..126b0df1d3e5 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/deploy_paymaster.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/deploy_paymaster.rs @@ -11,7 +11,7 @@ use zkstack_cli_config::{ }; use crate::{ - messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + messages::MSG_CHAIN_NOT_INITIALIZED, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -39,19 +39,12 @@ pub async fn deploy_paymaster( shell, DEPLOY_PAYMASTER_SCRIPT_PARAMS.input(&chain_config.link_to_code), )?; - let secrets = chain_config.get_secrets_config()?; + let secrets = chain_config.get_secrets_config().await?; let mut forge = Forge::new(&foundry_contracts_path) .script(&DEPLOY_PAYMASTER_SCRIPT_PARAMS.script(), forge_args.clone()) .with_ffi() - .with_rpc_url( - secrets - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(), - ); + .with_rpc_url(secrets.get("l1.l1_rpc_url")?); if let Some(address) = sender { forge = forge.with_sender(address); diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/enable_evm_emulator.rs b/zkstack_cli/crates/zkstack/src/commands/chain/enable_evm_emulator.rs index e15cdbacf103..2e80fb8ab7b7 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/enable_evm_emulator.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/enable_evm_emulator.rs @@ -1,13 +1,13 @@ use anyhow::Context; use xshell::Shell; use zkstack_cli_common::{forge::ForgeScriptArgs, logger}; -use zkstack_cli_config::{traits::ReadConfigWithBasePath, EcosystemConfig, GenesisConfig}; +use zkstack_cli_config::{raw::RawConfig, EcosystemConfig, GENESIS_FILE}; +use zksync_types::H256; use crate::{ enable_evm_emulator::enable_evm_emulator, messages::{ MSG_CHAIN_NOT_INITIALIZED, MSG_EVM_EMULATOR_ENABLED, MSG_EVM_EMULATOR_HASH_MISSING_ERR, - MSG_L1_SECRETS_MUST_BE_PRESENTED, }, }; @@ -17,22 +17,18 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { .load_current_chain() .context(MSG_CHAIN_NOT_INITIALIZED)?; - let default_genesis_config = GenesisConfig::read_with_base_path( - shell, - EcosystemConfig::default_configs_path(&chain_config.link_to_code), - )?; + let genesis_config_path = + EcosystemConfig::default_configs_path(&chain_config.link_to_code).join(GENESIS_FILE); + let default_genesis_config = RawConfig::read(shell, genesis_config_path).await?; - let has_evm_emulation_support = default_genesis_config.evm_emulator_hash.is_some(); + let has_evm_emulation_support = default_genesis_config + .get_opt::("evm_emulator_hash")? + .is_some(); anyhow::ensure!(has_evm_emulation_support, MSG_EVM_EMULATOR_HASH_MISSING_ERR); let contracts = chain_config.get_contracts_config()?; - let secrets = chain_config.get_secrets_config()?; - let l1_rpc_url = secrets - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + let secrets = chain_config.get_secrets_config().await?; + let l1_rpc_url = secrets.get("l1.l1_rpc_url")?; enable_evm_emulator( shell, diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/gateway_upgrade.rs b/zkstack_cli/crates/zkstack/src/commands/chain/gateway_upgrade.rs index f096daef032a..f61f0926369c 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/gateway_upgrade.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/gateway_upgrade.rs @@ -25,15 +25,14 @@ use zkstack_cli_config::{ ChainConfig, EcosystemConfig, }; use zkstack_cli_types::L1BatchCommitmentMode; -use zksync_basic_types::U256; -use zksync_types::Address; +use zksync_basic_types::{Address, U256}; use crate::{ commands::dev::commands::gateway::{ check_chain_readiness, fetch_chain_info, get_admin_call_builder, set_upgrade_timestamp_calldata, DAMode, GatewayUpgradeArgsInner, GatewayUpgradeInfo, }, - messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + messages::MSG_CHAIN_NOT_INITIALIZED, utils::forge::{fill_forge_private_key, WalletOwner}, }; @@ -97,12 +96,9 @@ pub async fn run(args: GatewayUpgradeArgs, shell: &Shell) -> anyhow::Result<()> .context(MSG_CHAIN_NOT_INITIALIZED)?; let l1_url = chain_config - .get_secrets_config()? - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + .get_secrets_config() + .await? + .get("l1.l1_rpc_url")?; match args.chain_upgrade_stage { GatewayChainUpgradeStage::PrepareStage1 => { @@ -138,31 +134,27 @@ async fn prepare_stage1( // No need to save it, we have enough for now let mut contracts_config = chain_config.get_contracts_config()?; - let general_config = chain_config.get_general_config()?; - let genesis_config = chain_config.get_genesis_config()?; + let general_config = chain_config.get_general_config().await?; + let genesis_config = chain_config.get_genesis_config().await?; let upgrade_info = GatewayUpgradeInfo::from_gateway_ecosystem_upgrade( contracts_config.ecosystem_contracts.bridgehub_proxy_addr, gateway_ecosystem_preparation_output, ); - let da_mode: DAMode = - if genesis_config.l1_batch_commit_data_generator_mode == L1BatchCommitmentMode::Rollup { - DAMode::PermanentRollup - } else { - DAMode::Validium - }; + let commitment_mode = + genesis_config.get::("l1_batch_commit_data_generator_mode")?; + let da_mode = match commitment_mode { + L1BatchCommitmentMode::Rollup => DAMode::PermanentRollup, + L1BatchCommitmentMode::Validium => DAMode::Validium, + }; let chain_info = fetch_chain_info( &upgrade_info, &GatewayUpgradeArgsInner { chain_id: chain_config.chain_id.as_u64(), l1_rpc_url: l1_url, - l2_rpc_url: general_config - .api_config - .context("api config")? - .web3_json_rpc - .http_url, + l2_rpc_url: general_config.get("api.web3_json_rpc.http_url")?, validator_addr1: chain_config.get_wallets_config()?.operator.address, validator_addr2: chain_config.get_wallets_config()?.blob_operator.address, da_mode, @@ -265,19 +257,13 @@ async fn finalize_stage1( println!("Finalizing stage1 of chain upgrade!"); let contracts_config = chain_config.get_contracts_config()?; - let general_config = chain_config.get_general_config()?; - let genesis_config = chain_config.get_genesis_config()?; + let general_config = chain_config.get_general_config().await?; + let genesis_config = chain_config.get_genesis_config().await?; println!("Checking chain readiness..."); check_chain_readiness( l1_url.clone(), - general_config - .api_config - .as_ref() - .context("api")? - .web3_json_rpc - .http_url - .clone(), + general_config.get("api.web3_json_rpc.http_url")?, chain_config.chain_id.as_u64(), ) .await?; @@ -287,12 +273,12 @@ async fn finalize_stage1( let gateway_ecosystem_preparation_output = GatewayEcosystemUpgradeOutput::read_with_base_path(shell, &ecosystem_config.config)?; - let da_mode: DAMode = - if genesis_config.l1_batch_commit_data_generator_mode == L1BatchCommitmentMode::Rollup { - DAMode::PermanentRollup - } else { - DAMode::Validium - }; + let commitment_mode = + genesis_config.get::("l1_batch_commit_data_generator_mode")?; + let da_mode = match commitment_mode { + L1BatchCommitmentMode::Rollup => DAMode::PermanentRollup, + L1BatchCommitmentMode::Validium => DAMode::Validium, + }; let upgrade_info = GatewayUpgradeInfo::from_gateway_ecosystem_upgrade( contracts_config.ecosystem_contracts.bridgehub_proxy_addr, @@ -301,11 +287,7 @@ async fn finalize_stage1( let args = GatewayUpgradeArgsInner { chain_id: chain_config.chain_id.as_u64(), l1_rpc_url: l1_url.clone(), - l2_rpc_url: general_config - .api_config - .context("api config")? - .web3_json_rpc - .http_url, + l2_rpc_url: general_config.get("api.web3_json_rpc.http_url")?, validator_addr1: chain_config.get_wallets_config()?.operator.address, validator_addr2: chain_config.get_wallets_config()?.blob_operator.address, da_mode, @@ -313,15 +295,10 @@ async fn finalize_stage1( }; let chain_info = fetch_chain_info(&upgrade_info, &args).await?; - let admin_calls_finalize = get_admin_call_builder(&upgrade_info, &chain_info, args); - admin_calls_finalize.display(); - let admin_calldata = admin_calls_finalize.compile_full_calldata(); - call_chain_admin(l1_url, chain_config, admin_calldata).await?; - println!("done!"); Ok(()) diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/genesis/database.rs b/zkstack_cli/crates/zkstack/src/commands/chain/genesis/database.rs index fe12bc017a45..cda28883d0f3 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/genesis/database.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/genesis/database.rs @@ -8,8 +8,8 @@ use zkstack_cli_common::{ logger, }; use zkstack_cli_config::{ - override_config, set_file_artifacts, set_rocks_db_config, set_server_database, - traits::SaveConfigWithBasePath, ChainConfig, EcosystemConfig, FileArtifacts, + override_config, set_file_artifacts, set_rocks_db_config, set_server_database, ChainConfig, + EcosystemConfig, FileArtifacts, }; use zkstack_cli_types::ProverMode; use zksync_basic_types::commitment::L1BatchCommitmentMode; @@ -34,10 +34,10 @@ pub async fn run(args: GenesisArgs, shell: &Shell) -> anyhow::Result<()> { .load_current_chain() .context(MSG_CHAIN_NOT_INITIALIZED)?; - let mut secrets = chain_config.get_secrets_config()?; - let args = args.fill_values_with_secrets(&chain_config)?; + let mut secrets = chain_config.get_secrets_config().await?.patched(); + let args = args.fill_values_with_secrets(&chain_config).await?; set_server_database(&mut secrets, &args.server_db)?; - secrets.save_with_base_path(shell, &chain_config.configs)?; + secrets.save().await?; initialize_server_database( shell, @@ -78,7 +78,7 @@ pub async fn initialize_server_database( Ok(()) } -pub fn update_configs( +pub async fn update_configs( args: GenesisArgsFinal, shell: &Shell, config: &ChainConfig, @@ -86,18 +86,18 @@ pub fn update_configs( shell.create_dir(&config.rocks_db_path)?; // Update secrets configs - let mut secrets = config.get_secrets_config()?; + let mut secrets = config.get_secrets_config().await?.patched(); set_server_database(&mut secrets, &args.server_db)?; - secrets.save_with_base_path(shell, &config.configs)?; + secrets.save().await?; // Update general config - let mut general = config.get_general_config()?; + let mut general = config.get_general_config().await?.patched(); let rocks_db = recreate_rocksdb_dirs(shell, &config.rocks_db_path, RocksDBDirOption::Main) .context(MSG_RECREATE_ROCKS_DB_ERRROR)?; let file_artifacts = FileArtifacts::new(config.artifacts.clone()); set_rocks_db_config(&mut general, rocks_db)?; - set_file_artifacts(&mut general, file_artifacts); - general.save_with_base_path(shell, &config.configs)?; + set_file_artifacts(&mut general, file_artifacts)?; + general.save().await?; let link_to_code = config.link_to_code.clone(); if config.prover_version != ProverMode::NoProofs { diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/genesis/mod.rs b/zkstack_cli/crates/zkstack/src/commands/chain/genesis/mod.rs index 5cb289d32609..ee4bd90b3e15 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/genesis/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/genesis/mod.rs @@ -7,7 +7,7 @@ use zkstack_cli_config::{ChainConfig, EcosystemConfig}; use crate::{ commands::chain::{ args::genesis::{GenesisArgs, GenesisArgsFinal}, - genesis::{self, database::initialize_server_database, server::run_server_genesis}, + genesis::{database::initialize_server_database, server::run_server_genesis}, }, messages::{ MSG_CHAIN_NOT_INITIALIZED, MSG_GENESIS_COMPLETED, MSG_INITIALIZING_DATABASES_SPINNER, @@ -63,7 +63,7 @@ pub async fn genesis( shell: &Shell, config: &ChainConfig, ) -> anyhow::Result<()> { - genesis::database::update_configs(args.clone(), shell, config)?; + database::update_configs(args.clone(), shell, config).await?; logger::note( MSG_SELECTED_CONFIG, diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/genesis/server.rs b/zkstack_cli/crates/zkstack/src/commands/chain/genesis/server.rs index 9a52595c978c..9e61d8f402e4 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/genesis/server.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/genesis/server.rs @@ -7,7 +7,7 @@ use zkstack_cli_common::{ }; use zkstack_cli_config::{ traits::FileConfigWithDefaultName, ChainConfig, ContractsConfig, EcosystemConfig, - GeneralConfig, GenesisConfig, SecretsConfig, WalletsConfig, + WalletsConfig, GENERAL_FILE, GENESIS_FILE, SECRETS_FILE, }; use crate::messages::{ @@ -35,10 +35,10 @@ pub fn run_server_genesis(chain_config: &ChainConfig, shell: &Shell) -> anyhow:: .run( shell, ServerMode::Genesis, - GenesisConfig::get_path_with_base_path(&chain_config.configs), + chain_config.configs.join(GENESIS_FILE), WalletsConfig::get_path_with_base_path(&chain_config.configs), - GeneralConfig::get_path_with_base_path(&chain_config.configs), - SecretsConfig::get_path_with_base_path(&chain_config.configs), + chain_config.configs.join(GENERAL_FILE), + chain_config.configs.join(SECRETS_FILE), ContractsConfig::get_path_with_base_path(&chain_config.configs), None, vec![], diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs b/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs index 4db4c9927de1..4c74e194c4ee 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs @@ -1,12 +1,11 @@ use anyhow::Context; -use ethers::types::Address; use xshell::Shell; use zkstack_cli_common::logger; use zkstack_cli_config::{ copy_configs, set_l1_rpc_url, traits::SaveConfigWithBasePath, update_from_chain_config, ChainConfig, ContractsConfig, EcosystemConfig, }; -use zksync_config::configs::DataAvailabilitySecrets; +use zksync_types::Address; use crate::{ commands::{ @@ -21,11 +20,11 @@ use crate::{ portal::update_portal_config, }, messages::{ - MSG_CHAIN_CONFIGS_INITIALIZED, MSG_CHAIN_NOT_FOUND_ERR, MSG_CONSENSUS_CONFIG_MISSING_ERR, + MSG_CHAIN_CONFIGS_INITIALIZED, MSG_CHAIN_NOT_FOUND_ERR, MSG_PORTAL_FAILED_TO_CREATE_CONFIG_ERR, }, utils::{ - consensus::{generate_consensus_keys, get_consensus_secrets, get_genesis_specs}, + consensus::{generate_consensus_keys, set_consensus_secrets, set_genesis_specs}, ports::EcosystemPortsScanner, }, }; @@ -61,52 +60,31 @@ pub async fn init_configs( )?; } - let consensus_keys = generate_consensus_keys(); - - // Initialize secrets config - let mut secrets = chain_config.get_secrets_config()?; - set_l1_rpc_url(&mut secrets, init_args.l1_rpc_url.clone())?; - secrets.consensus = Some(get_consensus_secrets(&consensus_keys)); - - let mut general_config = chain_config.get_general_config()?; - - if general_config.proof_data_handler_config.is_some() && general_config.prover_gateway.is_some() - { - let proof_data_handler_config = general_config.proof_data_handler_config.clone().unwrap(); - let mut prover_gateway = general_config.prover_gateway.clone().unwrap(); - - prover_gateway.api_url = - format!("http://127.0.0.1:{}", proof_data_handler_config.http_port); - - general_config.prover_gateway = Some(prover_gateway); + let mut general_config = chain_config.get_general_config().await?.patched(); + let prover_data_handler_port = general_config + .base() + .get_opt::("proof_data_handler.http_port")?; + if let Some(port) = prover_data_handler_port { + general_config.insert("prover_gateway.api_url", format!("http://127.0.0.1:{port}"))?; } - let mut consensus_config = general_config - .consensus_config - .context(MSG_CONSENSUS_CONFIG_MISSING_ERR)?; - - consensus_config.genesis_spec = Some(get_genesis_specs(chain_config, &consensus_keys)); + let consensus_keys = generate_consensus_keys(); + set_genesis_specs(&mut general_config, chain_config, &consensus_keys)?; - general_config.consensus_config = Some(consensus_config); - if let Some(validium_config) = init_args.validium_config.clone() { - match validium_config { - ValidiumType::NoDA => { - general_config.da_client_config = None; - } - ValidiumType::Avail((avail_config, avail_secrets)) => { - general_config.da_client_config = Some(avail_config.into()); - secrets.data_availability = Some(DataAvailabilitySecrets::Avail(avail_secrets)); - } + match &init_args.validium_config { + None | Some(ValidiumType::NoDA) => { + general_config.remove("da_client"); + } + Some(ValidiumType::Avail((avail_config, _))) => { + general_config.insert_yaml("da_client.avail", avail_config)?; } } - - secrets.save_with_base_path(shell, &chain_config.configs)?; - general_config.save_with_base_path(shell, &chain_config.configs)?; + general_config.save().await?; // Initialize genesis config - let mut genesis_config = chain_config.get_genesis_config()?; + let mut genesis_config = chain_config.get_genesis_config().await?.patched(); update_from_chain_config(&mut genesis_config, chain_config)?; - genesis_config.save_with_base_path(shell, &chain_config.configs)?; + genesis_config.save().await?; // Initialize contracts config let mut contracts_config = ecosystem_config.get_contracts_config()?; @@ -115,12 +93,24 @@ pub async fn init_configs( contracts_config.l1.chain_admin_addr = Address::zero(); contracts_config.l1.base_token_addr = chain_config.base_token.address; contracts_config.l1.base_token_asset_id = Some(encode_ntv_asset_id( - genesis_config.l1_chain_id.0.into(), + chain_config.l1_network.chain_id().into(), contracts_config.l1.base_token_addr, )); contracts_config.save_with_base_path(shell, &chain_config.configs)?; - genesis::database::update_configs(init_args.genesis_args.clone(), shell, chain_config)?; + // Initialize secrets config + let mut secrets = chain_config.get_secrets_config().await?.patched(); + set_l1_rpc_url(&mut secrets, init_args.l1_rpc_url.clone())?; + set_consensus_secrets(&mut secrets, &consensus_keys)?; + match &init_args.validium_config { + None | Some(ValidiumType::NoDA) => { /* Do nothing */ } + Some(ValidiumType::Avail((_, avail_secrets))) => { + secrets.insert_yaml("da.avail", avail_secrets)?; + } + } + secrets.save().await?; + + genesis::database::update_configs(init_args.genesis_args.clone(), shell, chain_config).await?; update_portal_config(shell, chain_config) .await diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/init/mod.rs b/zkstack_cli/crates/zkstack/src/commands/chain/init/mod.rs index f115048d1181..b2638e989d3f 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/init/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/init/mod.rs @@ -2,9 +2,10 @@ use anyhow::Context; use clap::{command, Parser, Subcommand}; use xshell::Shell; use zkstack_cli_common::{git, logger, spinner::Spinner}; -use zkstack_cli_config::{traits::SaveConfigWithBasePath, ChainConfig, EcosystemConfig}; +use zkstack_cli_config::{ + get_da_client_type, traits::SaveConfigWithBasePath, ChainConfig, EcosystemConfig, +}; use zkstack_cli_types::{BaseToken, L1BatchCommitmentMode}; -use zksync_config::DAClientConfig; use zksync_types::Address; use crate::{ @@ -175,7 +176,9 @@ pub async fn init( .await?; contracts_config.save_with_base_path(shell, &chain_config.configs)?; - let l1_da_validator_addr = get_l1_da_validator(chain_config); + let l1_da_validator_addr = get_l1_da_validator(chain_config) + .await + .context("l1_da_validator_addr")?; let spinner = Spinner::new(MSG_DA_PAIR_REGISTRATION_SPINNER); set_da_validator_pair( @@ -184,7 +187,7 @@ pub async fn init( contracts_config.l1.chain_admin_addr, &chain_config.get_wallets_config()?.governor, contracts_config.l1.diamond_proxy_addr, - l1_da_validator_addr.context("l1_da_validator_addr")?, + l1_da_validator_addr, contracts_config .l2 .da_validator_addr @@ -245,21 +248,19 @@ pub async fn init( Ok(()) } -pub(crate) fn get_l1_da_validator(chain_config: &ChainConfig) -> anyhow::Result
{ +pub(crate) async fn get_l1_da_validator(chain_config: &ChainConfig) -> anyhow::Result
{ let contracts_config = chain_config.get_contracts_config()?; let l1_da_validator_contract = match chain_config.l1_batch_commit_data_generator_mode { L1BatchCommitmentMode::Rollup => contracts_config.l1.rollup_l1_da_validator_addr, L1BatchCommitmentMode::Validium => { - let general_config = chain_config.get_general_config()?; - if let Some(da_client_config) = general_config.da_client_config { - match da_client_config { - DAClientConfig::Avail(_) => contracts_config.l1.avail_l1_da_validator_addr, - DAClientConfig::NoDA => contracts_config.l1.no_da_validium_l1_validator_addr, - _ => anyhow::bail!("DA client config is not supported"), + let general_config = chain_config.get_general_config().await?; + match get_da_client_type(&general_config) { + Some("avail") => contracts_config.l1.avail_l1_da_validator_addr, + Some("no_da") | None => contracts_config.l1.no_da_validium_l1_validator_addr, + Some(unsupported) => { + anyhow::bail!("DA client config is not supported: {unsupported:?}"); } - } else { - contracts_config.l1.no_da_validium_l1_validator_addr } } } diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/migrate_from_gateway.rs b/zkstack_cli/crates/zkstack/src/commands/chain/migrate_from_gateway.rs index cf9b9e8e6399..b2ebcda80a85 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/migrate_from_gateway.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/migrate_from_gateway.rs @@ -25,14 +25,12 @@ use zkstack_cli_config::{ EcosystemConfig, }; use zkstack_cli_types::L1BatchCommitmentMode; -use zksync_basic_types::{ - pubdata_da::PubdataSendingMode, settlement::SettlementMode, H256, U256, U64, -}; +use zksync_basic_types::{settlement::SettlementMode, H256, U256, U64}; use zksync_types::L2ChainId; use zksync_web3_decl::client::{Client, L2}; use crate::{ - messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + messages::MSG_CHAIN_NOT_INITIALIZED, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -74,17 +72,14 @@ pub async fn run(args: MigrateFromGatewayArgs, shell: &Shell) -> anyhow::Result< .context("Gateway config not present")?; let l1_url = chain_config - .get_secrets_config()? - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + .get_secrets_config() + .await? + .get::("l1.l1_rpc_url")?; - let genesis_config = chain_config.get_genesis_config()?; + let genesis_config = chain_config.get_genesis_config().await?; let is_rollup = matches!( - genesis_config.l1_batch_commit_data_generator_mode, + genesis_config.get("l1_batch_commit_data_generator_mode")?, L1BatchCommitmentMode::Rollup ); @@ -126,29 +121,13 @@ pub async fn run(args: MigrateFromGatewayArgs, shell: &Shell) -> anyhow::Result< ) .await?; - let gateway_provider = Provider::::try_from( - gateway_chain_config - .get_general_config() - .unwrap() - .api_config - .unwrap() - .web3_json_rpc - .http_url, - )?; + let general_config = gateway_chain_config.get_general_config().await?; + let l2_rpc_url = general_config.get::("api.web3_json_rpc.http_url")?; + let gateway_provider = Provider::::try_from(&l2_rpc_url)?; - let client: Client = Client::http( - gateway_chain_config - .get_general_config() - .unwrap() - .api_config - .unwrap() - .web3_json_rpc - .http_url - .parse() - .unwrap(), - )? - .for_network(L2::from(L2ChainId::new(gateway_chain_id).unwrap())) - .build(); + let client: Client = Client::http(l2_rpc_url.parse().context("invalid L2 RPC URL")?)? + .for_network(L2::from(L2ChainId::new(gateway_chain_id).unwrap())) + .build(); if hash == H256::zero() { println!("Chain already migrated!"); @@ -188,43 +167,23 @@ pub async fn run(args: MigrateFromGatewayArgs, shell: &Shell) -> anyhow::Result< gateway_chain_chain_config.gateway_chain_id = 0u64.into(); gateway_chain_chain_config.save_with_base_path(shell, chain_config.configs.clone())?; - let mut general_config = chain_config.get_general_config().unwrap(); - - let eth_config = general_config.eth.as_mut().context("eth")?; - - eth_config - .gas_adjuster - .as_mut() - .expect("gas_adjuster") - .settlement_mode = SettlementMode::SettlesToL1; + let mut general_config = chain_config.get_general_config().await?.patched(); + general_config.insert_yaml( + "eth.gas_adjuster.settlement_mode", + SettlementMode::SettlesToL1, + )?; if is_rollup { - // For rollups, new type of commitment should be used, but - // not for validium. - eth_config - .sender - .as_mut() - .expect("sender") - .pubdata_sending_mode = PubdataSendingMode::Blobs; + // `PubdataSendingMode` has differing `serde` and file-based config serializations, hence + // we supply a raw string value. + general_config.insert("eth.sender.pubdata_sending_mode", "BLOBS")?; } - eth_config - .sender - .as_mut() - .context("sender")? - .wait_confirmations = Some(0); + general_config.insert("eth.sender.wait_confirmations", 0)?; + // Undoing what was changed during migration to gateway. // TODO(EVM-925): maybe remove this logic. - eth_config - .sender - .as_mut() - .expect("sender") - .max_aggregated_tx_gas = 15000000; - eth_config - .sender - .as_mut() - .expect("sender") - .max_eth_tx_data_size = 120_000; - - general_config.save_with_base_path(shell, chain_config.configs.clone())?; + general_config.insert("eth.sender.max_aggregated_tx_gas", 15000000)?; + general_config.insert("eth.sender.max_eth_tx_data_size", 120_000)?; + general_config.save().await?; Ok(()) } diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/migrate_to_gateway.rs b/zkstack_cli/crates/zkstack/src/commands/chain/migrate_to_gateway.rs index c51f6414ce97..0be8c62da627 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/migrate_to_gateway.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/migrate_to_gateway.rs @@ -24,14 +24,12 @@ use zkstack_cli_config::{ EcosystemConfig, }; use zkstack_cli_types::L1BatchCommitmentMode; -use zksync_basic_types::{ - pubdata_da::PubdataSendingMode, settlement::SettlementMode, Address, H256, U256, U64, -}; +use zksync_basic_types::{settlement::SettlementMode, Address, H256, U256, U64}; use zksync_config::configs::gateway::GatewayChainConfig; use zksync_system_constants::L2_BRIDGEHUB_ADDRESS; use crate::{ - messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + messages::MSG_CHAIN_NOT_INITIALIZED, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -84,14 +82,11 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() .context("Gateway config not present")?; let l1_url = chain_config - .get_secrets_config()? - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + .get_secrets_config() + .await? + .get::("l1.l1_rpc_url")?; - let genesis_config = chain_config.get_genesis_config()?; + let genesis_config = chain_config.get_genesis_config().await?; let preparation_config_path = GATEWAY_PREPARATION.input(&ecosystem_config.link_to_code); let preparation_config = GatewayPreparationConfig::new( @@ -176,15 +171,9 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() .await? .governance_l2_tx_hash; - let gateway_provider = Provider::::try_from( - gateway_chain_config - .get_general_config() - .unwrap() - .api_config - .unwrap() - .web3_json_rpc - .http_url, - )?; + let general_config = gateway_chain_config.get_general_config().await?; + let l2_rpc_url = general_config.get::("api.web3_json_rpc.http_url")?; + let gateway_provider = Provider::::try_from(l2_rpc_url.clone())?; if hash == H256::zero() { println!("Chain already migrated!"); @@ -214,7 +203,7 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() let chain_contracts_config = chain_config.get_contracts_config().unwrap(); let is_rollup = matches!( - genesis_config.l1_batch_commit_data_generator_mode, + genesis_config.get("l1_batch_commit_data_generator_mode")?, L1BatchCommitmentMode::Rollup ); @@ -356,19 +345,10 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() hex::encode(hash.as_bytes()) ); - let gateway_url = gateway_chain_config - .get_general_config() - .unwrap() - .api_config - .unwrap() - .web3_json_rpc - .http_url - .clone(); - - let mut chain_secrets_config = chain_config.get_secrets_config().unwrap(); - chain_secrets_config.l1.as_mut().unwrap().gateway_rpc_url = - Some(url::Url::parse(&gateway_url).unwrap().into()); - chain_secrets_config.save_with_base_path(shell, chain_config.configs.clone())?; + let gateway_url = l2_rpc_url; + let mut chain_secrets_config = chain_config.get_secrets_config().await?.patched(); + chain_secrets_config.insert("l1.gateway_rpc_url", gateway_url)?; + chain_secrets_config.save().await?; let gateway_chain_config = GatewayChainConfig::from_gateway_and_chain_data( &gateway_gateway_config, @@ -378,43 +358,21 @@ pub async fn run(args: MigrateToGatewayArgs, shell: &Shell) -> anyhow::Result<() ); gateway_chain_config.save_with_base_path(shell, chain_config.configs.clone())?; - let mut general_config = chain_config.get_general_config().unwrap(); - - let eth_config = general_config.eth.as_mut().context("eth")?; + let mut general_config = chain_config.get_general_config().await?.patched(); + general_config.insert_yaml("eth.gas_adjuster.settlement_mode", SettlementMode::Gateway)?; - eth_config - .gas_adjuster - .as_mut() - .expect("gas_adjuster") - .settlement_mode = SettlementMode::Gateway; if is_rollup { - // For rollups, new type of commitment should be used, but - // not for validium. - eth_config - .sender - .as_mut() - .expect("sender") - .pubdata_sending_mode = PubdataSendingMode::RelayedL2Calldata; + // For rollups, new type of commitment should be used, but not for validium. + // `PubdataSendingMode` has differing `serde` and file-based config serializations, hence + // we supply a raw string value. + general_config.insert("eth.sender.pubdata_sending_mode", "RELAYED_L2_CALLDATA")?; } - eth_config - .sender - .as_mut() - .context("sender")? - .wait_confirmations = Some(0); + general_config.insert("eth.sender.wait_confirmations", 0)?; // TODO(EVM-925): the number below may not always work, especially for large prices on // top of Gateway. This field would have to be either not used on GW or transformed into u64. - eth_config - .sender - .as_mut() - .expect("sender") - .max_aggregated_tx_gas = 4294967295; - eth_config - .sender - .as_mut() - .expect("sender") - .max_eth_tx_data_size = 550_000; - - general_config.save_with_base_path(shell, chain_config.configs.clone())?; + general_config.insert("eth.sender.max_aggregated_tx_gas", 4294967295_u64)?; + general_config.insert("eth.sender.max_eth_tx_data_size", 550_000)?; + general_config.save().await?; Ok(()) } diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/mod.rs b/zkstack_cli/crates/zkstack/src/commands/chain/mod.rs index d6c1851d0c96..2c8930f33ceb 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/mod.rs @@ -92,7 +92,7 @@ pub enum ChainCommands { pub(crate) async fn run(shell: &Shell, args: ChainCommands) -> anyhow::Result<()> { match args { - ChainCommands::Create(args) => create::run(args, shell), + ChainCommands::Create(args) => create::run(args, shell).await, ChainCommands::Init(args) => init::run(*args, shell).await, ChainCommands::BuildTransactions(args) => build_transactions::run(args, shell).await, ChainCommands::Genesis(args) => genesis::run(args, shell).await, diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/register_chain.rs b/zkstack_cli/crates/zkstack/src/commands/chain/register_chain.rs index 626d25438385..4711e8645b27 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/register_chain.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/register_chain.rs @@ -15,10 +15,7 @@ use zkstack_cli_config::{ }; use crate::{ - messages::{ - MSG_CHAIN_NOT_INITIALIZED, MSG_CHAIN_REGISTERED, MSG_L1_SECRETS_MUST_BE_PRESENTED, - MSG_REGISTERING_CHAIN_SPINNER, - }, + messages::{MSG_CHAIN_NOT_INITIALIZED, MSG_CHAIN_REGISTERED, MSG_REGISTERING_CHAIN_SPINNER}, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -28,13 +25,8 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { .load_current_chain() .context(MSG_CHAIN_NOT_INITIALIZED)?; let mut contracts = chain_config.get_contracts_config()?; - let secrets = chain_config.get_secrets_config()?; - let l1_rpc_url = secrets - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + let secrets = chain_config.get_secrets_config().await?; + let l1_rpc_url = secrets.get("l1.l1_rpc_url")?; let spinner = Spinner::new(MSG_REGISTERING_CHAIN_SPINNER); register_chain( shell, diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/set_token_multiplier_setter.rs b/zkstack_cli/crates/zkstack/src/commands/chain/set_token_multiplier_setter.rs index e1a57dcd0f00..1d17d179afdc 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/set_token_multiplier_setter.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/set_token_multiplier_setter.rs @@ -15,9 +15,9 @@ use zksync_basic_types::Address; use crate::{ messages::{ - MSG_CHAIN_NOT_INITIALIZED, MSG_L1_SECRETS_MUST_BE_PRESENTED, - MSG_TOKEN_MULTIPLIER_SETTER_UPDATED_TO, MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER, - MSG_WALLETS_CONFIG_MUST_BE_PRESENT, MSG_WALLET_TOKEN_MULTIPLIER_SETTER_NOT_FOUND, + MSG_CHAIN_NOT_INITIALIZED, MSG_TOKEN_MULTIPLIER_SETTER_UPDATED_TO, + MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER, MSG_WALLETS_CONFIG_MUST_BE_PRESENT, + MSG_WALLET_TOKEN_MULTIPLIER_SETTER_NOT_FOUND, }, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -38,12 +38,9 @@ pub async fn run(args: ForgeScriptArgs, shell: &Shell) -> anyhow::Result<()> { .context(MSG_CHAIN_NOT_INITIALIZED)?; let contracts_config = chain_config.get_contracts_config()?; let l1_url = chain_config - .get_secrets_config()? - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(); + .get_secrets_config() + .await? + .get("l1.l1_rpc_url")?; let token_multiplier_setter_address = chain_config .get_wallets_config() .context(MSG_WALLETS_CONFIG_MUST_BE_PRESENT)? diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/setup_legacy_bridge.rs b/zkstack_cli/crates/zkstack/src/commands/chain/setup_legacy_bridge.rs index 24ef9d3c16d9..4d3e3cb1bd4e 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/setup_legacy_bridge.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/setup_legacy_bridge.rs @@ -13,7 +13,7 @@ use zkstack_cli_config::{ }; use crate::{ - messages::{MSG_DEPLOYING_PAYMASTER, MSG_L1_SECRETS_MUST_BE_PRESENTED}, + messages::MSG_DEPLOYING_PAYMASTER, utils::forge::{check_the_balance, fill_forge_private_key, WalletOwner}, }; @@ -52,19 +52,12 @@ pub async fn setup_legacy_bridge( }; let foundry_contracts_path = chain_config.path_to_l1_foundry(); input.save(shell, SETUP_LEGACY_BRIDGE.input(&chain_config.link_to_code))?; - let secrets = chain_config.get_secrets_config()?; + let secrets = chain_config.get_secrets_config().await?; let mut forge = Forge::new(&foundry_contracts_path) .script(&SETUP_LEGACY_BRIDGE.script(), forge_args.clone()) .with_ffi() - .with_rpc_url( - secrets - .l1 - .context(MSG_L1_SECRETS_MUST_BE_PRESENTED)? - .l1_rpc_url - .expose_str() - .to_string(), - ) + .with_rpc_url(secrets.get("l1.l1_rpc_url")?) .with_broadcast(); forge = fill_forge_private_key( diff --git a/zkstack_cli/crates/zkstack/src/commands/consensus/conv.rs b/zkstack_cli/crates/zkstack/src/commands/consensus/conv.rs deleted file mode 100644 index c9d878c8fd32..000000000000 --- a/zkstack_cli/crates/zkstack/src/commands/consensus/conv.rs +++ /dev/null @@ -1,47 +0,0 @@ -use anyhow::Context as _; -use zksync_config::configs::consensus as config; -use zksync_consensus_crypto::TextFmt as _; -use zksync_consensus_roles::attester; -use zksync_protobuf::{ProtoFmt, ProtoRepr}; - -use super::proto; -use crate::utils::consensus::parse_attester_committee; - -#[derive(Debug, Clone, PartialEq)] -pub(super) struct SetAttesterCommitteeFile { - pub attesters: attester::Committee, -} - -impl ProtoFmt for SetAttesterCommitteeFile { - type Proto = proto::SetAttesterCommitteeFile; - - fn read(r: &Self::Proto) -> anyhow::Result { - // zksync_config was not allowed to depend on consensus crates, - // therefore to parse the config we need to go through the intermediate - // representation of consensus types defined in zksync_config. - let attesters: Vec<_> = r - .attesters - .iter() - .map(|x| x.read()) - .collect::>() - .context("attesters")?; - Ok(Self { - attesters: parse_attester_committee(&attesters)?, - }) - } - - fn build(&self) -> Self::Proto { - Self::Proto { - attesters: self - .attesters - .iter() - .map(|a| { - ProtoRepr::build(&config::WeightedAttester { - key: config::AttesterPublicKey(a.key.encode()), - weight: a.weight, - }) - }) - .collect(), - } - } -} diff --git a/zkstack_cli/crates/zkstack/src/commands/consensus/mod.rs b/zkstack_cli/crates/zkstack/src/commands/consensus/mod.rs index ad64207b481c..3a628d561e81 100644 --- a/zkstack_cli/crates/zkstack/src/commands/consensus/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/consensus/mod.rs @@ -3,7 +3,6 @@ use std::{borrow::Borrow, collections::HashMap, path::PathBuf, sync::Arc}; /// Consensus registry contract operations. /// Includes code duplicated from `zksync_node_consensus::registry::abi`. use anyhow::Context as _; -use conv::*; use ethers::{ abi::Detokenize, contract::{FunctionCall, Multicall}, @@ -16,15 +15,11 @@ use tokio::time::MissedTickBehavior; use xshell::Shell; use zkstack_cli_common::{config::global_config, logger, wallets::Wallet}; use zkstack_cli_config::EcosystemConfig; +use zksync_basic_types::L2ChainId; use zksync_consensus_crypto::ByteFmt; use zksync_consensus_roles::{attester, validator}; -use crate::{commands::args::WaitArgs, messages, utils::consensus::parse_attester_committee}; - -mod conv; -mod proto; -#[cfg(test)] -mod tests; +use crate::{commands::args::WaitArgs, messages, utils::consensus::read_attester_committee_yaml}; #[allow(warnings)] mod abi { @@ -99,17 +94,20 @@ pub enum Command { /// Collection of sent transactions. #[derive(Default)] -pub struct TxSet(Vec<(H256, &'static str)>); +struct TxSet(Vec<(H256, String)>); impl TxSet { /// Sends a transactions and stores the transaction hash. - pub async fn send, D: Detokenize>( + async fn send, D: Detokenize>( &mut self, - name: &'static str, + name: String, call: FunctionCall, ) -> anyhow::Result<()> { - let h = call.send().await.context(name)?.tx_hash(); - self.0.push((h, name)); + let hash = call.send().await.with_context(|| name.clone())?.tx_hash(); + if global_config().verbose { + logger::debug(format!("Sent transaction {name}: {hash:?}")); + } + self.0.push((hash, name)); Ok(()) } @@ -146,19 +144,14 @@ fn print_attesters(committee: &attester::Committee) { struct Setup { chain: zkstack_cli_config::ChainConfig, contracts: zkstack_cli_config::ContractsConfig, - general: zkstack_cli_config::GeneralConfig, - genesis: zkstack_cli_config::GenesisConfig, + l2_chain_id: L2ChainId, + l2_http_url: String, + genesis_attesters: attester::Committee, } impl Setup { fn provider(&self) -> anyhow::Result> { - let l2_url = &self - .general - .api_config - .as_ref() - .context(messages::MSG_API_CONFIG_MISSING)? - .web3_json_rpc - .http_url; + let l2_url = &self.l2_http_url; Provider::try_from(l2_url).with_context(|| format!("Provider::try_from({l2_url})")) } @@ -173,7 +166,7 @@ impl Setup { .multicall3 .context(messages::MSG_MULTICALL3_CONTRACT_NOT_CONFIGURED)?, ), - Some(self.genesis.l2_chain_id.as_u64()), + Some(self.l2_chain_id.as_u64()), )?) } @@ -186,7 +179,7 @@ impl Setup { } fn signer(&self, wallet: LocalWallet) -> anyhow::Result> { - let wallet = wallet.with_chain_id(self.genesis.l2_chain_id.as_u64()); + let wallet = wallet.with_chain_id(self.l2_chain_id.as_u64()); let provider = self.provider().context("provider()")?; let signer = SignerMiddleware::new(provider, wallet.clone()); // Allows us to send next transaction without waiting for the previous to complete. @@ -194,7 +187,7 @@ impl Setup { Ok(Arc::new(signer)) } - fn new(shell: &Shell) -> anyhow::Result { + async fn new(shell: &Shell) -> anyhow::Result { let ecosystem_config = EcosystemConfig::from_file(shell).context("EcosystemConfig::from_file()")?; let chain = ecosystem_config @@ -203,13 +196,29 @@ impl Setup { let contracts = chain .get_contracts_config() .context("get_contracts_config()")?; - let genesis = chain.get_genesis_config().context("get_genesis_config()")?; - let general = chain.get_general_config().context("get_general_config()")?; + let l2_chain_id = chain + .get_genesis_config() + .await + .context("get_genesis_config()")? + .get("l2_chain_id")?; + + let general = chain + .get_general_config() + .await + .context("get_general_config()")?; + // We're getting a parent path here, since we need object input with the `attesters` array + let genesis_attesters = general + .get_raw("consensus.genesis_spec") + .context(messages::MSG_CONSENSUS_GENESIS_SPEC_ATTESTERS_MISSING_IN_GENERAL_YAML)? + .clone(); + let genesis_attesters = read_attester_committee_yaml(genesis_attesters)?; + Ok(Self { chain, contracts, - general, - genesis, + l2_chain_id, + l2_http_url: general.get("api.web3_json_rpc.http_url")?, + genesis_attesters, }) } @@ -260,26 +269,10 @@ impl Setup { // Fetch the desired state. if let Some(path) = &opts.from_file { let yaml = std::fs::read_to_string(path).context("read_to_string()")?; - let file: SetAttesterCommitteeFile = zksync_protobuf::serde::Deserialize { - deny_unknown_fields: true, - } - .proto_fmt_from_yaml(&yaml) - .context("proto_fmt_from_yaml()")?; - return Ok(file.attesters); + let yaml = serde_yaml::from_str(&yaml).context("parse YAML")?; + return read_attester_committee_yaml(yaml); } - let attesters = (|| { - Some( - &self - .general - .consensus_config - .as_ref()? - .genesis_spec - .as_ref()? - .attesters, - ) - })() - .context(messages::MSG_CONSENSUS_GENESIS_SPEC_ATTESTERS_MISSING_IN_GENERAL_YAML)?; - parse_attester_committee(attesters).context("parse_attester_committee()") + Ok(self.genesis_attesters.clone()) } async fn wait_for_registry_contract_inner( @@ -335,9 +328,21 @@ impl Setup { } async fn set_attester_committee(&self, want: &attester::Committee) -> anyhow::Result<()> { + if global_config().verbose { + logger::debug(format!("Setting attester committee: {want:?}")); + } + let provider = self.provider().context("provider()")?; let block_id = self.last_block(&provider).await.context("last_block()")?; + if global_config().verbose { + logger::debug(format!("Fetched latest L2 block: {block_id:?}")); + } + let governor = self.governor().context("governor()")?; + if global_config().verbose { + logger::debug(format!("Using governor: {:?}", governor.address)); + } + let signer = self.signer( governor .private_key @@ -348,6 +353,13 @@ impl Setup { .consensus_registry(signer.clone()) .context("consensus_registry()")?; let mut multicall = self.multicall(signer).context("multicall()")?; + if global_config().verbose { + logger::debug(format!( + "Using consensus registry at {:?}, multicall at {:?}", + consensus_registry.address(), + multicall.contract.address() + )); + } let owner = consensus_registry.owner().call().await.context("owner()")?; if owner != governor.address { @@ -368,6 +380,11 @@ impl Setup { .try_into() .ok() .context("num_nodes() overflow")?; + if global_config().verbose { + logger::debug(format!( + "Fetched number of nodes from consensus registry: {n}" + )); + } multicall.block = Some(block_id); let node_owners: Vec
= multicall @@ -379,6 +396,12 @@ impl Setup { .await .context("node_owners()")?; multicall.clear_calls(); + if global_config().verbose { + logger::debug(format!( + "Fetched node owners from consensus registry: {node_owners:?}" + )); + } + let nodes: Vec = multicall .add_calls( false, @@ -390,6 +413,11 @@ impl Setup { .await .context("nodes()")?; multicall.clear_calls(); + if global_config().verbose { + logger::debug(format!( + "Fetched node info from consensus registry: {nodes:?}" + )); + } // Update the state. let mut txs = TxSet::default(); @@ -398,15 +426,21 @@ impl Setup { if node.attester_latest.removed { continue; } + + let node_owner = node_owners[i]; let got = attester::WeightedAttester { key: decode_attester_key(&node.attester_latest.pub_key) .context("decode_attester_key()")?, weight: node.attester_latest.weight.into(), }; + if let Some(weight) = to_insert.remove(&got.key) { if weight != got.weight { txs.send( - "changed_attester_weight", + format!( + "change_attester_weight({node_owner:?}, {} -> {weight})", + got.weight + ), consensus_registry.change_attester_weight( node_owners[i], weight.try_into().context("weight overflow")?, @@ -415,18 +449,24 @@ impl Setup { .await?; } if !node.attester_latest.active { - txs.send("activate", consensus_registry.activate(node_owners[i])) - .await?; + txs.send( + format!("activate({node_owner:?})"), + consensus_registry.activate(node_owner), + ) + .await?; } } else { - txs.send("remove", consensus_registry.remove(node_owners[i])) - .await?; + txs.send( + format!("remove({node_owner:?})"), + consensus_registry.remove(node_owner), + ) + .await?; } } for (key, weight) in to_insert { let vk = validator::SecretKey::generate(); txs.send( - "add", + format!("add({key:?}, {weight})"), consensus_registry.add( Address::random(), /*validator_weight=*/ 1, @@ -439,7 +479,7 @@ impl Setup { .await?; } txs.send( - "commit_attester_committee", + "commit_attester_committee".to_owned(), consensus_registry.commit_attester_committee(), ) .await?; @@ -450,7 +490,7 @@ impl Setup { impl Command { pub(crate) async fn run(self, shell: &Shell) -> anyhow::Result<()> { - let setup = Setup::new(shell).context("Setup::new()")?; + let setup = Setup::new(shell).await?; match self { Self::SetAttesterCommittee(opts) => { let want = setup diff --git a/zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.proto b/zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.proto deleted file mode 100644 index d8a7323f7144..000000000000 --- a/zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -package zksync.toolbox.consensus; - -import "zksync/core/consensus.proto"; - -message SetAttesterCommitteeFile { - repeated core.consensus.WeightedAttester attesters = 1; -} diff --git a/zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.rs b/zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.rs deleted file mode 100644 index 61a0a047f0a9..000000000000 --- a/zkstack_cli/crates/zkstack/src/commands/consensus/proto/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![allow(warnings)] - -include!(concat!( - env!("OUT_DIR"), - "/src/commands/consensus/proto/gen.rs" -)); diff --git a/zkstack_cli/crates/zkstack/src/commands/consensus/tests.rs b/zkstack_cli/crates/zkstack/src/commands/consensus/tests.rs deleted file mode 100644 index c2f393ad2294..000000000000 --- a/zkstack_cli/crates/zkstack/src/commands/consensus/tests.rs +++ /dev/null @@ -1,19 +0,0 @@ -use rand::{distributions::Distribution, Rng}; -use zksync_consensus_utils::EncodeDist; -use zksync_protobuf::testonly::{test_encode_all_formats, FmtConv}; - -use super::SetAttesterCommitteeFile; - -impl Distribution for EncodeDist { - fn sample(&self, rng: &mut R) -> SetAttesterCommitteeFile { - SetAttesterCommitteeFile { - attesters: rng.gen(), - } - } -} - -#[test] -fn test_encoding() { - let rng = &mut rand::thread_rng(); - test_encode_all_formats::>(rng); -} diff --git a/zkstack_cli/crates/zkstack/src/commands/contract_verifier/wait.rs b/zkstack_cli/crates/zkstack/src/commands/contract_verifier/wait.rs index 0b844df61f4c..070d64b5bdb4 100644 --- a/zkstack_cli/crates/zkstack/src/commands/contract_verifier/wait.rs +++ b/zkstack_cli/crates/zkstack/src/commands/contract_verifier/wait.rs @@ -13,11 +13,9 @@ pub(crate) async fn wait(shell: &Shell, args: WaitArgs) -> anyhow::Result<()> { let verbose = global_config().verbose; let prometheus_port = chain - .get_general_config()? - .contract_verifier - .as_ref() - .context("contract verifier config not specified")? - .prometheus_port; + .get_general_config() + .await? + .get("contract_verifier.prometheus_port")?; logger::info("Waiting for contract verifier to become alive"); args.poll_prometheus(prometheus_port, verbose).await?; logger::info(format!( diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/check_sqlx_data.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/check_sqlx_data.rs index abead3fe00d6..8957fc46ede9 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/check_sqlx_data.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/check_sqlx_data.rs @@ -14,7 +14,7 @@ use crate::commands::dev::{ }, }; -pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { +pub async fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { let args = args.parse(); if args.selected_dals.none() { logger::outro(MSG_NO_DATABASES_SELECTED); @@ -25,7 +25,7 @@ pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { logger::info(msg_database_info(MSG_DATABASE_CHECK_SQLX_DATA_GERUND)); - let dals = get_dals(shell, &args.selected_dals, &args.urls)?; + let dals = get_dals(shell, &args.selected_dals, &args.urls).await?; for dal in dals { check_sqlx_data(shell, &ecosystem_config.link_to_code, dal)?; } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/drop.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/drop.rs index e46a434cec06..f2bf647ed726 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/drop.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/drop.rs @@ -23,7 +23,7 @@ pub async fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> logger::info(msg_database_info(MSG_DATABASE_DROP_GERUND)); - let dals = get_dals(shell, &args.selected_dals, &args.urls)?; + let dals = get_dals(shell, &args.selected_dals, &args.urls).await?; for dal in dals { drop_database(dal).await?; } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/migrate.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/migrate.rs index 8c21262c0712..3ebb30c3dfe3 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/migrate.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/migrate.rs @@ -13,7 +13,7 @@ use crate::commands::dev::{ }, }; -pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { +pub async fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { let args = args.parse(); if args.selected_dals.none() { logger::outro(MSG_NO_DATABASES_SELECTED); @@ -23,7 +23,7 @@ pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { logger::info(msg_database_info(MSG_DATABASE_MIGRATE_GERUND)); let ecosystem_config = EcosystemConfig::from_file(shell)?; - let dals = get_dals(shell, &args.selected_dals, &args.urls)?; + let dals = get_dals(shell, &args.selected_dals, &args.urls).await?; for dal in dals { migrate_database(shell, &ecosystem_config.link_to_code, dal)?; } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/mod.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/mod.rs index ed039fc65019..909b6fa5bc22 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/mod.rs @@ -37,12 +37,12 @@ pub enum DatabaseCommands { pub async fn run(shell: &Shell, args: DatabaseCommands) -> anyhow::Result<()> { match args { - DatabaseCommands::CheckSqlxData(args) => check_sqlx_data::run(shell, args), + DatabaseCommands::CheckSqlxData(args) => check_sqlx_data::run(shell, args).await, DatabaseCommands::Drop(args) => drop::run(shell, args).await, - DatabaseCommands::Migrate(args) => migrate::run(shell, args), - DatabaseCommands::NewMigration(args) => new_migration::run(shell, args), - DatabaseCommands::Prepare(args) => prepare::run(shell, args), + DatabaseCommands::Migrate(args) => migrate::run(shell, args).await, + DatabaseCommands::NewMigration(args) => new_migration::run(shell, args).await, + DatabaseCommands::Prepare(args) => prepare::run(shell, args).await, DatabaseCommands::Reset(args) => reset::run(shell, args).await, - DatabaseCommands::Setup(args) => setup::run(shell, args), + DatabaseCommands::Setup(args) => setup::run(shell, args).await, } } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/new_migration.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/new_migration.rs index 655a841e060a..809886eb5e2f 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/new_migration.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/new_migration.rs @@ -10,12 +10,12 @@ use crate::commands::dev::{ messages::{msg_database_new_migration_loading, MSG_DATABASE_NEW_MIGRATION_SUCCESS}, }; -pub fn run(shell: &Shell, args: DatabaseNewMigrationArgs) -> anyhow::Result<()> { +pub async fn run(shell: &Shell, args: DatabaseNewMigrationArgs) -> anyhow::Result<()> { let args = args.fill_values_with_prompt(); let dal = match args.selected_database { - SelectedDatabase::Core => get_core_dal(shell, None)?, - SelectedDatabase::Prover => get_prover_dal(shell, None)?, + SelectedDatabase::Core => get_core_dal(shell, None).await?, + SelectedDatabase::Prover => get_prover_dal(shell, None).await?, }; let ecosystem_config = EcosystemConfig::from_file(shell)?; diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/prepare.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/prepare.rs index 82c9ed2e338b..1d2c2d57cb03 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/prepare.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/prepare.rs @@ -13,7 +13,7 @@ use crate::commands::dev::{ }, }; -pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { +pub async fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { let args = args.parse(); if args.selected_dals.none() { logger::outro(MSG_NO_DATABASES_SELECTED); @@ -24,7 +24,7 @@ pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { logger::info(msg_database_info(MSG_DATABASE_PREPARE_GERUND)); - let dals = get_dals(shell, &args.selected_dals, &args.urls)?; + let dals = get_dals(shell, &args.selected_dals, &args.urls).await?; for dal in dals { prepare_sqlx_data(shell, &ecosystem_config.link_to_code, dal)?; } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/reset.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/reset.rs index 4a9ec022d723..c9d5ad112ae3 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/reset.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/reset.rs @@ -24,7 +24,7 @@ pub async fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> logger::info(msg_database_info(MSG_DATABASE_RESET_GERUND)); - let dals = get_dals(shell, &args.selected_dals, &args.urls)?; + let dals = get_dals(shell, &args.selected_dals, &args.urls).await?; for dal in dals { logger::info(msg_database_loading(MSG_DATABASE_RESET_GERUND, &dal.path)); reset_database(shell, ecosystem_config.link_to_code.clone(), dal).await?; diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/setup.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/setup.rs index 4eba9b615fc0..ca44273ebf1a 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/setup.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/database/setup.rs @@ -13,7 +13,7 @@ use crate::commands::dev::{ }, }; -pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { +pub async fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { let args = args.parse(); if args.selected_dals.none() { logger::outro(MSG_NO_DATABASES_SELECTED); @@ -24,7 +24,7 @@ pub fn run(shell: &Shell, args: DatabaseCommonArgs) -> anyhow::Result<()> { logger::info(msg_database_info(MSG_DATABASE_SETUP_GERUND)); - let dals = get_dals(shell, &args.selected_dals, &args.urls)?; + let dals = get_dals(shell, &args.selected_dals, &args.urls).await?; for dal in dals { setup_database(shell, &ecosystem_config.link_to_code, dal)?; } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/genesis.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/genesis.rs index 8e7a3973e037..29e5f734e528 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/genesis.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/genesis.rs @@ -18,7 +18,7 @@ pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { .context(MSG_CHAIN_NOT_FOUND_ERR)?; let spinner = Spinner::new(MSG_GENESIS_FILE_GENERATION_STARTED); let secrets_path = chain.path_to_secrets_config(); - let dal = get_core_dal(shell, None)?; + let dal = get_core_dal(shell, None).await?; reset_database(shell, ecosystem.link_to_code, dal).await?; Cmd::new(cmd!(shell,"cargo run --package genesis_generator --bin genesis_generator -- --config-path={secrets_path}")).run()?; spinner.finish(); diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/prover/info.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/prover/info.rs index 44d80d48d0ce..d90b350b0192 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/prover/info.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/prover/info.rs @@ -84,14 +84,7 @@ pub(crate) async fn get_fflonk_snark_wrapper(link_to_prover: &Path) -> anyhow::R } pub(crate) async fn get_database_url(chain: &ChainConfig) -> anyhow::Result { - let prover_url = chain - .get_secrets_config()? - .database - .context("Database secrets not found")? - .prover_url()? - .expose_url() - .to_string(); - Ok(prover_url) + chain.get_secrets_config().await?.get("database.prover_url") } pub fn parse_version(version: &str) -> anyhow::Result<(&str, &str)> { diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/args.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/args.rs index 1cc65f194cdc..8395796781da 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/args.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/args.rs @@ -4,9 +4,7 @@ use xshell::Shell; use zkstack_cli_config::EcosystemConfig; use crate::{ - commands::dev::messages::{ - MSG_API_CONFIG_NOT_FOUND_ERR, MSG_STATUS_PORTS_HELP, MSG_STATUS_URL_HELP, - }, + commands::dev::messages::{MSG_STATUS_PORTS_HELP, MSG_STATUS_URL_HELP}, messages::MSG_CHAIN_NOT_FOUND_ERR, }; @@ -25,7 +23,7 @@ pub struct StatusArgs { } impl StatusArgs { - pub fn get_url(&self, shell: &Shell) -> anyhow::Result { + pub async fn get_url(&self, shell: &Shell) -> anyhow::Result { if let Some(url) = &self.url { Ok(url.clone()) } else { @@ -33,13 +31,9 @@ impl StatusArgs { let chain = ecosystem .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let general_config = chain.get_general_config()?; - let health_check_port = general_config - .api_config - .context(MSG_API_CONFIG_NOT_FOUND_ERR)? - .healthcheck - .port; - Ok(format!("http://localhost:{}/health", health_check_port)) + let general_config = chain.get_general_config().await?; + let health_check_port = general_config.get::("api.healthcheck.port")?; + Ok(format!("http://localhost:{health_check_port}/health")) } } } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/mod.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/mod.rs index 7f2db7533655..7de264ca8252 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/status/mod.rs @@ -129,7 +129,7 @@ pub async fn run(shell: &Shell, args: StatusArgs) -> anyhow::Result<()> { return print_ports(shell); } - let health_check_url = args.get_url(shell)?; + let health_check_url = args.get_url(shell).await?; print_status(health_check_url) } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs index 64ea474fa2fa..07a0ae192618 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/loadtest.rs @@ -5,17 +5,14 @@ use zkstack_cli_config::EcosystemConfig; use crate::commands::dev::messages::MSG_CHAIN_NOT_FOUND_ERR; -pub fn run(shell: &Shell) -> anyhow::Result<()> { +pub async fn run(shell: &Shell) -> anyhow::Result<()> { let ecosystem_config = EcosystemConfig::from_file(shell)?; let chain_config = ecosystem_config .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let general_api = chain_config - .get_general_config()? - .api_config - .context("API config is not found")?; + let general_config = chain_config.get_general_config().await?; let mut command = cmd!( shell, @@ -24,9 +21,9 @@ pub fn run(shell: &Shell) -> anyhow::Result<()> { .env( "L2_CHAIN_ID", chain_config - .get_genesis_config()? - .l2_chain_id - .as_u64() + .get_genesis_config() + .await? + .get::("l2_chain_id")? .to_string(), ) .env( @@ -40,8 +37,14 @@ pub fn run(shell: &Shell) -> anyhow::Result<()> { .address ), ) - .env("L2_RPC_ADDRESS", general_api.web3_json_rpc.http_url) - .env("L2_WS_RPC_ADDRESS", general_api.web3_json_rpc.ws_url); + .env( + "L2_RPC_ADDRESS", + general_config.get::("api.web3_json_rpc.http_url")?, + ) + .env( + "L2_WS_RPC_ADDRESS", + general_config.get::("api.web3_json_rpc.ws_url")?, + ); if global_config().verbose { command = command.env("RUST_LOG", "loadnext=info") diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/mod.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/mod.rs index 095e27652aa0..90f9971508d3 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/mod.rs @@ -64,6 +64,6 @@ pub async fn run(shell: &Shell, args: TestCommands) -> anyhow::Result<()> { TestCommands::L1Contracts => l1_contracts::run(shell), TestCommands::Prover => prover::run(shell).await, TestCommands::Wallet => wallet::run(shell), - TestCommands::Loadtest => loadtest::run(shell), + TestCommands::Loadtest => loadtest::run(shell).await, } } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs index dce3cd9022da..7b93ebf974d6 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/rust.rs @@ -11,10 +11,7 @@ use crate::commands::dev::{ commands::test::db::reset_test_databases, dals::{Dal, CORE_DAL_PATH, PROVER_DAL_PATH}, defaults::{TEST_DATABASE_PROVER_URL, TEST_DATABASE_SERVER_URL}, - messages::{ - MSG_CHAIN_NOT_FOUND_ERR, MSG_POSTGRES_CONFIG_NOT_FOUND_ERR, MSG_UNIT_TESTS_RUN_SUCCESS, - MSG_USING_CARGO_NEXTEST, - }, + messages::{MSG_CHAIN_NOT_FOUND_ERR, MSG_UNIT_TESTS_RUN_SUCCESS, MSG_USING_CARGO_NEXTEST}, }; pub async fn run(shell: &Shell, args: RustArgs) -> anyhow::Result<()> { @@ -23,21 +20,13 @@ pub async fn run(shell: &Shell, args: RustArgs) -> anyhow::Result<()> { .clone() .load_chain(Some(ecosystem.default_chain)) .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let general_config = chain.get_general_config(); + let general_config = chain.get_general_config().await; let link_to_code = ecosystem.link_to_code; let (test_server_url, test_prover_url) = if let Ok(general_config) = general_config { - let postgres = general_config - .postgres_config - .context(MSG_POSTGRES_CONFIG_NOT_FOUND_ERR)?; - ( - postgres - .test_server_url - .context(MSG_POSTGRES_CONFIG_NOT_FOUND_ERR)?, - postgres - .test_prover_url - .context(MSG_POSTGRES_CONFIG_NOT_FOUND_ERR)?, + general_config.get::("postgres.test.server_url")?, + general_config.get::("postgres.test.prover_url")?, ) } else { ( diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/utils.rs b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/utils.rs index 7c042fad1fa9..871466d72c23 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/utils.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/commands/test/utils.rs @@ -56,12 +56,9 @@ impl TestWallets { let wallet = self.get_test_wallet(chain_config)?; let l1_rpc = chain_config - .get_secrets_config()? - .l1 - .context("No L1 secrets available")? - .l1_rpc_url - .expose_str() - .to_owned(); + .get_secrets_config() + .await? + .get::("l1.l1_rpc_url")?; let provider = Provider::::try_from(l1_rpc.clone())?; let balance = provider.get_balance(wallet.address, None).await?; diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/dals.rs b/zkstack_cli/crates/zkstack/src/commands/dev/dals.rs index 199c44bbeb70..0d1c505557f2 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/dals.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/dals.rs @@ -1,12 +1,9 @@ use anyhow::Context as _; use url::Url; use xshell::Shell; -use zkstack_cli_config::{EcosystemConfig, SecretsConfig}; +use zkstack_cli_config::{raw::RawConfig, EcosystemConfig}; -use super::{ - commands::database::args::DalUrls, - messages::{MSG_CHAIN_NOT_FOUND_ERR, MSG_DATABASE_MUST_BE_PRESENTED}, -}; +use super::{commands::database::args::DalUrls, messages::MSG_CHAIN_NOT_FOUND_ERR}; pub const CORE_DAL_PATH: &str = "core/lib/dal"; pub const PROVER_DAL_PATH: &str = "prover/crates/lib/prover_dal"; @@ -30,7 +27,7 @@ pub struct Dal { pub url: Url, } -pub fn get_dals( +pub async fn get_dals( shell: &Shell, selected_dals: &SelectedDals, urls: &DalUrls, @@ -38,27 +35,21 @@ pub fn get_dals( let mut dals = vec![]; if selected_dals.prover { - dals.push(get_prover_dal(shell, urls.prover.clone())?); + dals.push(get_prover_dal(shell, urls.prover.clone()).await?); } if selected_dals.core { - dals.push(get_core_dal(shell, urls.core.clone())?); + dals.push(get_core_dal(shell, urls.core.clone()).await?); } Ok(dals) } -pub fn get_prover_dal(shell: &Shell, url: Option) -> anyhow::Result { +pub async fn get_prover_dal(shell: &Shell, url: Option) -> anyhow::Result { let url = if let Some(url) = url { Url::parse(&url)? } else { - let secrets = get_secrets(shell)?; - secrets - .database - .as_ref() - .context(MSG_DATABASE_MUST_BE_PRESENTED)? - .prover_url()? - .expose_url() - .clone() + let secrets = get_secrets(shell).await?; + secrets.get("database.prover_url")? }; Ok(Dal { @@ -67,18 +58,12 @@ pub fn get_prover_dal(shell: &Shell, url: Option) -> anyhow::Result }) } -pub fn get_core_dal(shell: &Shell, url: Option) -> anyhow::Result { +pub async fn get_core_dal(shell: &Shell, url: Option) -> anyhow::Result { let url = if let Some(url) = url { Url::parse(&url)? } else { - let secrets = get_secrets(shell)?; - secrets - .database - .as_ref() - .context(MSG_DATABASE_MUST_BE_PRESENTED)? - .master_url()? - .expose_url() - .clone() + let secrets = get_secrets(shell).await?; + secrets.get("database.server_url")? }; Ok(Dal { @@ -87,12 +72,10 @@ pub fn get_core_dal(shell: &Shell, url: Option) -> anyhow::Result { }) } -fn get_secrets(shell: &Shell) -> anyhow::Result { +async fn get_secrets(shell: &Shell) -> anyhow::Result { let ecosystem_config = EcosystemConfig::from_file(shell)?; let chain_config = ecosystem_config .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let secrets = chain_config.get_secrets_config()?; - - Ok(secrets) + chain_config.get_secrets_config().await } diff --git a/zkstack_cli/crates/zkstack/src/commands/dev/messages.rs b/zkstack_cli/crates/zkstack/src/commands/dev/messages.rs index b65750b34341..68b79da70bda 100644 --- a/zkstack_cli/crates/zkstack/src/commands/dev/messages.rs +++ b/zkstack_cli/crates/zkstack/src/commands/dev/messages.rs @@ -48,7 +48,6 @@ pub(super) const MSG_DATABASE_RESET_GERUND: &str = "Resetting"; pub(super) const MSG_DATABASE_RESET_PAST: &str = "reset"; pub(super) const MSG_DATABASE_SETUP_GERUND: &str = "Setting up"; pub(super) const MSG_DATABASE_SETUP_PAST: &str = "set up"; -pub(super) const MSG_DATABASE_MUST_BE_PRESENTED: &str = "Database config must be presented"; pub(super) const MSG_DATABASE_COMMON_PROVER_HELP: &str = "Prover database"; pub(super) const MSG_DATABASE_COMMON_PROVER_URL_HELP: &str = "URL of the Prover database. If not specified, it is used from the current chain's secrets"; @@ -105,7 +104,6 @@ pub(super) const MSG_L1_CONTRACTS_ABOUT: &str = "Run L1 contracts tests"; pub(super) const MSG_L1_CONTRACTS_TEST_SUCCESS: &str = "L1 contracts tests ran successfully"; pub(super) const MSG_PROVER_TEST_ABOUT: &str = "Run prover tests"; pub(super) const MSG_PROVER_TEST_SUCCESS: &str = "Prover tests ran successfully"; -pub(super) const MSG_POSTGRES_CONFIG_NOT_FOUND_ERR: &str = "Postgres config not found"; pub(super) const MSG_RESETTING_TEST_DATABASES: &str = "Resetting test databases"; // Contract building related messages @@ -235,7 +233,6 @@ pub(super) const MSG_INVALID_L1_RPC_URL_ERR: &str = "Invalid L1 RPC URL"; // Status related messages pub(super) const MSG_STATUS_ABOUT: &str = "Get status of the server"; -pub(super) const MSG_API_CONFIG_NOT_FOUND_ERR: &str = "API config not found"; pub(super) const MSG_STATUS_URL_HELP: &str = "URL of the health check endpoint"; pub(super) const MSG_STATUS_PORTS_HELP: &str = "Show used ports"; pub(super) const MSG_COMPONENTS: &str = "Components:\n"; diff --git a/zkstack_cli/crates/zkstack/src/commands/ecosystem/common.rs b/zkstack_cli/crates/zkstack/src/commands/ecosystem/common.rs index 7255ba9e1ca5..f8c45cfcf8bd 100644 --- a/zkstack_cli/crates/zkstack/src/commands/ecosystem/common.rs +++ b/zkstack_cli/crates/zkstack/src/commands/ecosystem/common.rs @@ -1,16 +1,16 @@ -use anyhow::Context; use xshell::Shell; use zkstack_cli_common::forge::{Forge, ForgeScriptArgs}; use zkstack_cli_config::{ forge_interface::{ deploy_ecosystem::{ - input::{DeployL1Config, InitialDeploymentConfig}, + input::{DeployL1Config, GenesisInput, InitialDeploymentConfig}, output::DeployL1Output, }, script_params::DEPLOY_ECOSYSTEM_SCRIPT_PARAMS, }, - traits::{ReadConfig, ReadConfigWithBasePath, SaveConfig}, - ContractsConfig, EcosystemConfig, GenesisConfig, + raw::RawConfig, + traits::{ReadConfig, SaveConfig}, + ContractsConfig, EcosystemConfig, GENESIS_FILE, }; use zkstack_cli_types::{L1Network, ProverMode}; @@ -28,15 +28,14 @@ pub async fn deploy_l1( support_l2_legacy_shared_bridge_test: bool, ) -> anyhow::Result { let deploy_config_path = DEPLOY_ECOSYSTEM_SCRIPT_PARAMS.input(&config.link_to_code); - dbg!(config.get_default_configs_path()); - let default_genesis_config = - GenesisConfig::read_with_base_path(shell, config.get_default_configs_path()) - .context("failed reading genesis config")?; + let genesis_config_path = config.get_default_configs_path().join(GENESIS_FILE); + let default_genesis_config = RawConfig::read(shell, genesis_config_path).await?; + let default_genesis_input = GenesisInput::new(&default_genesis_config)?; let wallets_config = config.get_wallets()?; // For deploying ecosystem we only need genesis batch params let deploy_config = DeployL1Config::new( - &default_genesis_config, + &default_genesis_input, &wallets_config, initial_deployment_config, config.era_chain_id, diff --git a/zkstack_cli/crates/zkstack/src/commands/ecosystem/create.rs b/zkstack_cli/crates/zkstack/src/commands/ecosystem/create.rs index 404589afac2d..3bd936f69cb7 100644 --- a/zkstack_cli/crates/zkstack/src/commands/ecosystem/create.rs +++ b/zkstack_cli/crates/zkstack/src/commands/ecosystem/create.rs @@ -27,19 +27,19 @@ use crate::{ utils::link_to_code::resolve_link_to_code, }; -pub fn run(args: EcosystemCreateArgs, shell: &Shell) -> anyhow::Result<()> { +pub async fn run(args: EcosystemCreateArgs, shell: &Shell) -> anyhow::Result<()> { match EcosystemConfig::from_file(shell) { Ok(_) => bail!(MSG_ECOSYSTEM_ALREADY_EXISTS_ERR), Err(EcosystemConfigFromFileError::InvalidConfig { .. }) => { bail!(MSG_ECOSYSTEM_CONFIG_INVALID_ERR) } - Err(EcosystemConfigFromFileError::NotExists { .. }) => create(args, shell)?, + Err(EcosystemConfigFromFileError::NotExists { .. }) => create(args, shell).await?, }; Ok(()) } -fn create(args: EcosystemCreateArgs, shell: &Shell) -> anyhow::Result<()> { +async fn create(args: EcosystemCreateArgs, shell: &Shell) -> anyhow::Result<()> { let args = args .fill_values_with_prompt(shell) .context(MSG_ARGS_VALIDATOR_ERR)?; @@ -96,7 +96,7 @@ fn create(args: EcosystemCreateArgs, shell: &Shell) -> anyhow::Result<()> { spinner.finish(); let spinner = Spinner::new(MSG_CREATING_DEFAULT_CHAIN_SPINNER); - create_chain_inner(chain_config, &ecosystem_config, shell)?; + create_chain_inner(chain_config, &ecosystem_config, shell).await?; spinner.finish(); if args.start_containers { diff --git a/zkstack_cli/crates/zkstack/src/commands/ecosystem/gateway_upgrade.rs b/zkstack_cli/crates/zkstack/src/commands/ecosystem/gateway_upgrade.rs index 01905afb9a5d..b8179ca4db83 100644 --- a/zkstack_cli/crates/zkstack/src/commands/ecosystem/gateway_upgrade.rs +++ b/zkstack_cli/crates/zkstack/src/commands/ecosystem/gateway_upgrade.rs @@ -6,6 +6,7 @@ use xshell::Shell; use zkstack_cli_common::{db::DatabaseConfig, forge::Forge, git, spinner::Spinner}; use zkstack_cli_config::{ forge_interface::{ + deploy_ecosystem::input::GenesisInput, gateway_ecosystem_upgrade::{ input::GatewayEcosystemUpgradeInput, output::GatewayEcosystemUpgradeOutput, }, @@ -14,8 +15,9 @@ use zkstack_cli_config::{ FINALIZE_UPGRADE_SCRIPT_PARAMS, GATEWAY_PREPARATION, GATEWAY_UPGRADE_ECOSYSTEM_PARAMS, }, }, + raw::RawConfig, traits::{ReadConfig, ReadConfigWithBasePath, SaveConfig, SaveConfigWithBasePath}, - EcosystemConfig, GenesisConfig, CONFIGS_PATH, + EcosystemConfig, GENESIS_FILE, }; use zkstack_cli_types::ProverMode; use zksync_basic_types::commitment::L1BatchCommitmentMode; @@ -95,7 +97,11 @@ async fn no_governance_prepare( let forge_args = init_args.forge_args.clone(); let l1_rpc_url = init_args.l1_rpc_url.clone(); - let new_genesis_config = GenesisConfig::read_with_base_path(shell, CONFIGS_PATH)?; + let genesis_config_path = ecosystem_config + .get_default_configs_path() + .join(GENESIS_FILE); + let default_genesis_config = RawConfig::read(shell, genesis_config_path).await?; + let default_genesis_input = GenesisInput::new(&default_genesis_config)?; let current_contracts_config = ecosystem_config.get_contracts_config()?; let initial_deployment_config = ecosystem_config.get_initial_deployment_config()?; @@ -110,7 +116,7 @@ async fn no_governance_prepare( // assert_eq!(era_config.chain_id, ecosystem_config.era_chain_id); let gateway_upgrade_input = GatewayEcosystemUpgradeInput::new( - &new_genesis_config, + &default_genesis_input, ¤t_contracts_config, &initial_deployment_config, ecosystem_config.era_chain_id, @@ -492,7 +498,8 @@ async fn no_governance_stage_3( .load_chain(Some("gateway".to_string())) .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let chain_genesis_config = chain_config.get_genesis_config()?; + let chain_genesis_config = chain_config.get_genesis_config().await?; + let genesis_input = GenesisInput::new(&chain_genesis_config)?; let mut chain_contracts_config = chain_config.get_contracts_config()?; // Fund gateway's governor (chain_config.get_wallets_config()?.governor) @@ -521,7 +528,7 @@ async fn no_governance_stage_3( init_args.forge_args.clone(), ecosystem_config, &chain_config, - &chain_genesis_config, + &genesis_input, &ecosystem_config.get_initial_deployment_config().unwrap(), init_args.l1_rpc_url.clone(), ) @@ -587,7 +594,7 @@ async fn no_governance_stage_3( init_args.forge_args.clone(), ecosystem_config, &chain_config, - &chain_genesis_config, + &genesis_input, &ecosystem_config.get_initial_deployment_config().unwrap(), init_args.l1_rpc_url.clone(), ) diff --git a/zkstack_cli/crates/zkstack/src/commands/ecosystem/mod.rs b/zkstack_cli/crates/zkstack/src/commands/ecosystem/mod.rs index 19c2888edd0d..9e9277bb2084 100644 --- a/zkstack_cli/crates/zkstack/src/commands/ecosystem/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/ecosystem/mod.rs @@ -43,7 +43,7 @@ pub enum EcosystemCommands { pub(crate) async fn run(shell: &Shell, args: EcosystemCommands) -> anyhow::Result<()> { match args { - EcosystemCommands::Create(args) => create::run(args, shell), + EcosystemCommands::Create(args) => create::run(args, shell).await, EcosystemCommands::BuildTransactions(args) => build_transactions::run(args, shell).await, EcosystemCommands::Init(args) => init::run(args, shell).await, EcosystemCommands::ChangeDefaultChain(args) => change_default::run(args, shell), diff --git a/zkstack_cli/crates/zkstack/src/commands/explorer/init.rs b/zkstack_cli/crates/zkstack/src/commands/explorer/init.rs index 8bac0b84d982..d5c8b6f905f5 100644 --- a/zkstack_cli/crates/zkstack/src/commands/explorer/init.rs +++ b/zkstack_cli/crates/zkstack/src/commands/explorer/init.rs @@ -6,7 +6,7 @@ use zkstack_cli_common::{config::global_config, db, logger, Prompt}; use zkstack_cli_config::{ explorer::{ExplorerChainConfig, ExplorerConfig}, explorer_compose::{ExplorerBackendComposeConfig, ExplorerBackendConfig, ExplorerBackendPorts}, - traits::{ConfigWithL2RpcUrl, SaveConfig}, + traits::SaveConfig, ChainConfig, EcosystemConfig, }; @@ -41,14 +41,18 @@ pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { // Initialize explorer database initialize_explorer_database(&backend_config.database_url).await?; // Create explorer backend docker compose file - let l2_rpc_url = chain_config.get_general_config()?.get_l2_rpc_url()?; + let l2_rpc_url = chain_config + .get_general_config() + .await? + .get("api.web3_json_rpc.http_url")?; let backend_compose_config = ExplorerBackendComposeConfig::new(chain_name, l2_rpc_url, &backend_config)?; let backend_compose_config_path = ExplorerBackendComposeConfig::get_config_path(&shell.current_dir(), chain_name); backend_compose_config.save(shell, &backend_compose_config_path)?; // Add chain to explorer.json - let explorer_chain_config = build_explorer_chain_config(&chain_config, &backend_config)?; + let explorer_chain_config = + build_explorer_chain_config(&chain_config, &backend_config).await?; explorer_config.add_chain_config(&explorer_chain_config); } // Save explorer config @@ -100,19 +104,15 @@ fn fill_database_values_with_prompt(config: &ChainConfig) -> db::DatabaseConfig db::DatabaseConfig::new(explorer_db_url, explorer_db_name) } -fn build_explorer_chain_config( +async fn build_explorer_chain_config( chain_config: &ChainConfig, backend_config: &ExplorerBackendConfig, ) -> anyhow::Result { - let general_config = chain_config.get_general_config()?; + let general_config = chain_config.get_general_config().await?; // Get L2 RPC URL from general config - let l2_rpc_url = general_config.get_l2_rpc_url()?; + let l2_rpc_url = general_config.get("api.web3_json_rpc.http_url")?; // Get Verification API URL from general config - let verification_api_port = general_config - .contract_verifier - .as_ref() - .map(|verifier| verifier.port) - .context("verifier.port")?; + let verification_api_port = general_config.get::("contract_verifier.port")?; let verification_api_url = format!("http://127.0.0.1:{verification_api_port}"); // Build API URL let api_port = backend_config.ports.api_http_port; @@ -123,7 +123,7 @@ fn build_explorer_chain_config( name: chain_config.name.clone(), l2_network_name: chain_config.name.clone(), l2_chain_id: chain_config.chain_id.as_u64(), - rpc_url: l2_rpc_url.to_string(), + rpc_url: l2_rpc_url, api_url: api_url.to_string(), base_token_address: L2_BASE_TOKEN_ADDRESS.to_string(), hostnames: Vec::new(), diff --git a/zkstack_cli/crates/zkstack/src/commands/external_node/init.rs b/zkstack_cli/crates/zkstack/src/commands/external_node/init.rs index 526e9fd4bc5f..3e4dfb179200 100644 --- a/zkstack_cli/crates/zkstack/src/commands/external_node/init.rs +++ b/zkstack_cli/crates/zkstack/src/commands/external_node/init.rs @@ -4,16 +4,13 @@ use zkstack_cli_common::{ db::{drop_db_if_exists, init_db, migrate_db, DatabaseConfig}, spinner::Spinner, }; -use zkstack_cli_config::{ - traits::ReadConfigWithBasePath, ChainConfig, EcosystemConfig, SecretsConfig, -}; +use zkstack_cli_config::{raw::RawConfig, ChainConfig, EcosystemConfig, SECRETS_FILE}; use crate::{ consts::SERVER_MIGRATIONS, messages::{ - MSG_CHAIN_NOT_INITIALIZED, MSG_DATABASE_MUST_BE_PRESENTED, - MSG_EXTERNAL_NODE_CONFIG_NOT_INITIALIZED, MSG_FAILED_TO_DROP_SERVER_DATABASE_ERR, - MSG_INITIALIZING_DATABASES_SPINNER, + MSG_CHAIN_NOT_INITIALIZED, MSG_EXTERNAL_NODE_CONFIG_NOT_INITIALIZED, + MSG_FAILED_TO_DROP_SERVER_DATABASE_ERR, MSG_INITIALIZING_DATABASES_SPINNER, }, utils::rocks_db::{recreate_rocksdb_dirs, RocksDBDirOption}, }; @@ -30,21 +27,13 @@ pub async fn run(shell: &Shell) -> anyhow::Result<()> { pub async fn init(shell: &Shell, chain_config: &ChainConfig) -> anyhow::Result<()> { let spin = Spinner::new(MSG_INITIALIZING_DATABASES_SPINNER); - let secrets = SecretsConfig::read_with_base_path( - shell, - chain_config - .external_node_config_path - .clone() - .context(MSG_EXTERNAL_NODE_CONFIG_NOT_INITIALIZED)?, - )?; - let db_config = DatabaseConfig::from_url( - secrets - .database - .as_ref() - .context(MSG_DATABASE_MUST_BE_PRESENTED)? - .master_url()? - .expose_url(), - )?; + let secrets_path = chain_config + .external_node_config_path + .as_ref() + .context(MSG_EXTERNAL_NODE_CONFIG_NOT_INITIALIZED)? + .join(SECRETS_FILE); + let secrets = RawConfig::read(shell, secrets_path).await?; + let db_config = DatabaseConfig::from_url(&secrets.get("database.server_url")?)?; drop_db_if_exists(&db_config) .await .context(MSG_FAILED_TO_DROP_SERVER_DATABASE_ERR)?; diff --git a/zkstack_cli/crates/zkstack/src/commands/external_node/mod.rs b/zkstack_cli/crates/zkstack/src/commands/external_node/mod.rs index 7bd366d5871c..28dc29dcf5eb 100644 --- a/zkstack_cli/crates/zkstack/src/commands/external_node/mod.rs +++ b/zkstack_cli/crates/zkstack/src/commands/external_node/mod.rs @@ -28,7 +28,7 @@ pub enum ExternalNodeCommands { pub async fn run(shell: &Shell, commands: ExternalNodeCommands) -> anyhow::Result<()> { match commands { - ExternalNodeCommands::Configs(args) => prepare_configs::run(shell, args), + ExternalNodeCommands::Configs(args) => prepare_configs::run(shell, args).await, ExternalNodeCommands::Init => init::run(shell).await, ExternalNodeCommands::Build => build::build(shell).await, ExternalNodeCommands::Run(args) => run::run(shell, args).await, diff --git a/zkstack_cli/crates/zkstack/src/commands/external_node/prepare_configs.rs b/zkstack_cli/crates/zkstack/src/commands/external_node/prepare_configs.rs index ae36c1c9a1f4..9f0cd6bfbc8e 100644 --- a/zkstack_cli/crates/zkstack/src/commands/external_node/prepare_configs.rs +++ b/zkstack_cli/crates/zkstack/src/commands/external_node/prepare_configs.rs @@ -1,37 +1,30 @@ -use std::{collections::BTreeMap, path::Path, str::FromStr}; +use std::path::Path; use anyhow::Context; use xshell::Shell; use zkstack_cli_common::logger; use zkstack_cli_config::{ - external_node::ENConfig, - set_rocks_db_config, - traits::{FileConfigWithDefaultName, SaveConfigWithBasePath}, - ChainConfig, EcosystemConfig, GeneralConfig, SecretsConfig, -}; -use zksync_basic_types::url::SensitiveUrl; -use zksync_config::configs::{ - consensus::{ConsensusConfig, ConsensusSecrets, NodeSecretKey, Secret}, - DatabaseSecrets, L1Secrets, + raw::{PatchedConfig, RawConfig}, + set_rocks_db_config, ChainConfig, EcosystemConfig, CONSENSUS_CONFIG_FILE, EN_CONFIG_FILE, + GENERAL_FILE, SECRETS_FILE, }; +use zksync_basic_types::{L1ChainId, L2ChainId}; use zksync_consensus_crypto::TextFmt; use zksync_consensus_roles as roles; use crate::{ commands::external_node::args::prepare_configs::{PrepareConfigArgs, PrepareConfigFinal}, messages::{ - msg_preparing_en_config_is_done, MSG_CHAIN_NOT_INITIALIZED, - MSG_CONSENSUS_CONFIG_MISSING_ERR, MSG_CONSENSUS_SECRETS_MISSING_ERR, - MSG_CONSENSUS_SECRETS_NODE_KEY_MISSING_ERR, MSG_PREPARING_EN_CONFIGS, + msg_preparing_en_config_is_done, MSG_CHAIN_NOT_INITIALIZED, MSG_PREPARING_EN_CONFIGS, }, utils::{ - consensus::node_public_key, + consensus::{node_public_key, KeyAndAddress}, ports::EcosystemPortsScanner, rocks_db::{recreate_rocksdb_dirs, RocksDBDirOption}, }, }; -pub fn run(shell: &Shell, args: PrepareConfigArgs) -> anyhow::Result<()> { +pub async fn run(shell: &Shell, args: PrepareConfigArgs) -> anyhow::Result<()> { logger::info(MSG_PREPARING_EN_CONFIGS); let ecosystem_config = EcosystemConfig::from_file(shell)?; let mut chain_config = ecosystem_config @@ -44,104 +37,86 @@ pub fn run(shell: &Shell, args: PrepareConfigArgs) -> anyhow::Result<()> { .unwrap_or_else(|| chain_config.configs.join("external_node")); shell.create_dir(&external_node_config_path)?; chain_config.external_node_config_path = Some(external_node_config_path.clone()); - prepare_configs(shell, &chain_config, &external_node_config_path, args)?; + prepare_configs(shell, &chain_config, &external_node_config_path, args).await?; let chain_path = ecosystem_config.chains.join(&chain_config.name); chain_config.save_with_base_path(shell, chain_path)?; logger::info(msg_preparing_en_config_is_done(&external_node_config_path)); Ok(()) } -fn prepare_configs( +async fn prepare_configs( shell: &Shell, config: &ChainConfig, en_configs_path: &Path, args: PrepareConfigFinal, ) -> anyhow::Result<()> { let mut ports = EcosystemPortsScanner::scan(shell)?; - let genesis = config.get_genesis_config()?; - let general = config.get_general_config()?; + let genesis = config.get_genesis_config().await?; + let general = config.get_general_config().await?; let gateway = config.get_gateway_chain_config().ok(); - let en_config = ENConfig { - l2_chain_id: genesis.l2_chain_id, - l1_chain_id: genesis.l1_chain_id, - l1_batch_commit_data_generator_mode: genesis.l1_batch_commit_data_generator_mode, - main_node_url: SensitiveUrl::from_str( - &general - .api_config - .as_ref() - .context("api_config")? - .web3_json_rpc - .http_url, - )?, - main_node_rate_limit_rps: None, - bridge_addresses_refresh_interval_sec: None, - gateway_chain_id: gateway.map(|g| g.gateway_chain_id), - }; - let mut general_en = general.clone(); - general_en.consensus_config = None; + let l2_rpc_port = general.get::("api.web3_json_rpc.http_port")?; - let main_node_consensus_config = general - .consensus_config - .context(MSG_CONSENSUS_CONFIG_MISSING_ERR)?; - let mut en_consensus_config = main_node_consensus_config.clone(); + let mut en_config = PatchedConfig::empty(shell, en_configs_path.join(EN_CONFIG_FILE)); + en_config.insert( + "l2_chain_id", + genesis.get::("l2_chain_id")?.as_u64(), + )?; + en_config.insert("l1_chain_id", genesis.get::("l1_chain_id")?.0)?; + en_config.insert_yaml( + "l1_batch_commit_data_generator_mode", + genesis.get::("l1_batch_commit_data_generator_mode")?, + )?; + en_config.insert("main_node_url", format!("http://127.0.0.1:{l2_rpc_port}"))?; + if let Some(gateway) = &gateway { + en_config.insert_yaml("gateway_chain_id", gateway.gateway_chain_id)?; + } + en_config.save().await?; - let mut gossip_static_outbound = BTreeMap::new(); + // Copy and modify the general config + let general_config_path = en_configs_path.join(GENERAL_FILE); + shell.copy_file(config.path_to_general_config(), &general_config_path)?; + let mut general_en = RawConfig::read(shell, general_config_path.clone()) + .await? + .patched(); + let main_node_public_addr: String = general_en.base().get("consensus.public_addr")?; + let raw_consensus = general_en.base().get("consensus")?; + general_en.remove("consensus"); + + // Copy and modify the consensus config + let mut en_consensus_config = + PatchedConfig::empty(shell, en_configs_path.join(CONSENSUS_CONFIG_FILE)); + en_consensus_config.extend(raw_consensus); let main_node_public_key = node_public_key( &config - .get_secrets_config()? - .consensus - .context(MSG_CONSENSUS_SECRETS_MISSING_ERR)?, - )? - .context(MSG_CONSENSUS_SECRETS_NODE_KEY_MISSING_ERR)?; - gossip_static_outbound.insert(main_node_public_key, main_node_consensus_config.public_addr); - en_consensus_config.gossip_static_outbound = gossip_static_outbound; + .get_secrets_config() + .await? + .get::("consensus.node_key")?, + )?; + let gossip_static_outbound = [KeyAndAddress { + key: main_node_public_key, + addr: main_node_public_addr, + }]; + en_consensus_config.insert_yaml("gossip_static_outbound", gossip_static_outbound)?; + en_consensus_config.save().await?; // Set secrets config + let mut secrets = PatchedConfig::empty(shell, en_configs_path.join(SECRETS_FILE)); let node_key = roles::node::SecretKey::generate().encode(); - let consensus_secrets = ConsensusSecrets { - validator_key: None, - attester_key: None, - node_key: Some(NodeSecretKey(Secret::new(node_key))), - }; - - let gateway_rpc_url = if let Some(url) = args.gateway_rpc_url { - Some(SensitiveUrl::from_str(&url).context("gateway_url")?) - } else { - None - }; - let secrets = SecretsConfig { - consensus: Some(consensus_secrets), - database: Some(DatabaseSecrets { - server_url: Some(args.db.full_url().into()), - prover_url: None, - server_replica_url: None, - }), - l1: Some(L1Secrets { - l1_rpc_url: SensitiveUrl::from_str(&args.l1_rpc_url).context("l1_rpc_url")?, - gateway_rpc_url, - }), - data_availability: None, - }; + secrets.insert("consensus.node_key", node_key)?; + secrets.insert("database.server_url", args.db.full_url().to_string())?; + secrets.insert("l1.l1_rpc_url", args.l1_rpc_url)?; + if let Some(url) = args.gateway_rpc_url { + secrets.insert("l1.gateway_rpc_url", url)?; + } + secrets.save().await?; let dirs = recreate_rocksdb_dirs(shell, &config.rocks_db_path, RocksDBDirOption::ExternalNode)?; set_rocks_db_config(&mut general_en, dirs)?; - - general_en.save_with_base_path(shell, en_configs_path)?; - en_config.save_with_base_path(shell, en_configs_path)?; - en_consensus_config.save_with_base_path(shell, en_configs_path)?; - secrets.save_with_base_path(shell, en_configs_path)?; + general_en.save().await?; let offset = 0; // This is zero because general_en ports already have a chain offset - ports.allocate_ports_in_yaml( - shell, - &GeneralConfig::get_path_with_base_path(en_configs_path), - offset, - )?; - ports.allocate_ports_in_yaml( - shell, - &ConsensusConfig::get_path_with_base_path(en_configs_path), - offset, - )?; + ports.allocate_ports_in_yaml(shell, &general_config_path, offset)?; + ports.allocate_ports_in_yaml(shell, &en_configs_path.join(CONSENSUS_CONFIG_FILE), offset)?; Ok(()) } diff --git a/zkstack_cli/crates/zkstack/src/commands/external_node/wait.rs b/zkstack_cli/crates/zkstack/src/commands/external_node/wait.rs index b645314dc9c2..40adcb3f73a0 100644 --- a/zkstack_cli/crates/zkstack/src/commands/external_node/wait.rs +++ b/zkstack_cli/crates/zkstack/src/commands/external_node/wait.rs @@ -1,8 +1,7 @@ use anyhow::Context as _; use xshell::Shell; use zkstack_cli_common::{config::global_config, logger}; -use zkstack_cli_config::{traits::ReadConfigWithBasePath, EcosystemConfig}; -use zksync_config::configs::GeneralConfig; +use zkstack_cli_config::{raw::RawConfig, EcosystemConfig, GENERAL_FILE}; use crate::{ commands::args::WaitArgs, @@ -20,13 +19,8 @@ pub async fn wait(shell: &Shell, args: WaitArgs) -> anyhow::Result<()> { .external_node_config_path .clone() .context("External node is not initialized")?; - let general_config = GeneralConfig::read_with_base_path(shell, &en_path)?; - let health_check_port = general_config - .api_config - .as_ref() - .context("no API config")? - .healthcheck - .port; + let general_config = RawConfig::read(shell, en_path.join(GENERAL_FILE)).await?; + let health_check_port = general_config.get("api.healthcheck.port")?; logger::info(MSG_WAITING_FOR_EN); args.poll_health_check(health_check_port, verbose).await?; diff --git a/zkstack_cli/crates/zkstack/src/commands/portal.rs b/zkstack_cli/crates/zkstack/src/commands/portal.rs index d534498aaacd..15043abd509f 100644 --- a/zkstack_cli/crates/zkstack/src/commands/portal.rs +++ b/zkstack_cli/crates/zkstack/src/commands/portal.rs @@ -5,9 +5,7 @@ use ethers::types::Address; use xshell::Shell; use zkstack_cli_common::{config::global_config, docker, ethereum, logger}; use zkstack_cli_config::{ - portal::*, - traits::{ConfigWithL2RpcUrl, SaveConfig}, - AppsEcosystemConfig, ChainConfig, EcosystemConfig, + portal::*, traits::SaveConfig, AppsEcosystemConfig, ChainConfig, EcosystemConfig, }; use zkstack_cli_types::{BaseToken, TokenInfo}; @@ -24,14 +22,13 @@ async fn build_portal_chain_config( chain_config: &ChainConfig, ) -> anyhow::Result { // Get L2 RPC URL from general config - let l2_rpc_url = chain_config.get_general_config()?.get_l2_rpc_url()?; - // Get L1 RPC URL from secrects config - let secrets_config = chain_config.get_secrets_config()?; - let l1_rpc_url = secrets_config - .l1 - .as_ref() - .map(|l1| l1.l1_rpc_url.expose_str()) - .context("l1")?; + let l2_rpc_url = chain_config + .get_general_config() + .await? + .get("api.web3_json_rpc.http_url")?; + // Get L1 RPC URL from secrets config + let secrets_config = chain_config.get_secrets_config().await?; + let l1_rpc_url = secrets_config.get::("l1.l1_rpc_url")?; // Build L1 network config let l1_network = Some(L1NetworkConfig { id: chain_config.l1_network.chain_id(), @@ -40,10 +37,10 @@ async fn build_portal_chain_config( native_currency: TokenInfo::eth(), rpc_urls: RpcUrls { default: RpcUrlConfig { - http: vec![l1_rpc_url.to_string()], + http: vec![l1_rpc_url.clone()], }, public: RpcUrlConfig { - http: vec![l1_rpc_url.to_string()], + http: vec![l1_rpc_url.clone()], }, }, }); @@ -53,8 +50,7 @@ async fn build_portal_chain_config( } else { ( format!("{:?}", chain_config.base_token.address), - ethereum::get_token_info(chain_config.base_token.address, l1_rpc_url.to_string()) - .await?, + ethereum::get_token_info(chain_config.base_token.address, l1_rpc_url).await?, ) }; let tokens = vec![TokenConfig { @@ -70,7 +66,7 @@ async fn build_portal_chain_config( id: chain_config.chain_id.as_u64(), key: chain_config.name.clone(), name: chain_config.name.clone(), - rpc_url: l2_rpc_url.to_string(), + rpc_url: l2_rpc_url, l1_network, public_l1_network_id: None, block_explorer_url: None, diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/args/compressor_keys.rs b/zkstack_cli/crates/zkstack/src/commands/prover/args/compressor_keys.rs index 9de616657b20..9c9445bc3718 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/args/compressor_keys.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/args/compressor_keys.rs @@ -1,3 +1,5 @@ +use std::path::{Path, PathBuf}; + use clap::Parser; use zkstack_cli_common::Prompt; @@ -6,14 +8,14 @@ use crate::messages::MSG_SETUP_COMPRESSOR_KEY_PATH_PROMPT; #[derive(Debug, Clone, Parser, Default)] pub struct CompressorKeysArgs { #[clap(long)] - pub path: Option, + pub path: Option, } impl CompressorKeysArgs { - pub fn fill_values_with_prompt(self, default_path: &str) -> CompressorKeysArgs { + pub fn fill_values_with_prompt(self, default_path: &Path) -> CompressorKeysArgs { let path = self.path.unwrap_or_else(|| { Prompt::new(MSG_SETUP_COMPRESSOR_KEY_PATH_PROMPT) - .default(default_path) + .default(default_path.to_str().expect("non-UTF8 path")) .ask() }); diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs b/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs index 4956a23ac987..b0eabd4925ea 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/args/init.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; use slugify_rs::slugify; @@ -6,7 +8,6 @@ use url::Url; use xshell::Shell; use zkstack_cli_common::{db::DatabaseConfig, logger, Prompt, PromptConfirm, PromptSelect}; use zkstack_cli_config::ChainConfig; -use zksync_config::configs::fri_prover::CloudConnectionMode; use super::{ compressor_keys::CompressorKeysArgs, init_bellman_cuda::InitBellmanCudaArgs, @@ -92,24 +93,14 @@ enum ProofStoreConfig { GCS, } -#[derive( - Debug, Clone, ValueEnum, EnumIter, strum::Display, PartialEq, Eq, Deserialize, Serialize, -)] +#[derive(Debug, Clone, ValueEnum, EnumIter, strum::Display, PartialEq, Eq, Serialize)] #[allow(clippy::upper_case_acronyms)] -enum InternalCloudConnectionMode { +pub enum InternalCloudConnectionMode { GCP, + #[serde(rename = "LOCAL")] // match name in file-based configs Local, } -impl From for CloudConnectionMode { - fn from(cloud_type: InternalCloudConnectionMode) -> Self { - match cloud_type { - InternalCloudConnectionMode::GCP => CloudConnectionMode::GCP, - InternalCloudConnectionMode::Local => CloudConnectionMode::Local, - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize, Parser, Default)] pub struct ProofStorageGCSTmp { #[clap(long)] @@ -194,7 +185,7 @@ pub struct ProverInitArgsFinal { pub compressor_key_args: Option, pub setup_keys: Option, pub bellman_cuda_config: Option, - pub cloud_type: CloudConnectionMode, + pub cloud_type: InternalCloudConnectionMode, pub database_config: Option, } @@ -202,7 +193,7 @@ impl ProverInitArgs { pub(crate) fn fill_values_with_prompt( &self, shell: &Shell, - default_compressor_key_path: &str, + default_compressor_key_path: &Path, chain_config: &ChainConfig, ) -> anyhow::Result { let proof_store = self.fill_proof_storage_values_with_prompt(shell)?; @@ -355,11 +346,11 @@ impl ProverInitArgs { fn fill_setup_compressor_key_values_with_prompt( &self, - default_path: &str, + default_path: &Path, ) -> Option { if self.dev { return Some(CompressorKeysArgs { - path: Some(default_path.to_string()), + path: Some(default_path.to_owned()), }); } @@ -512,20 +503,18 @@ impl ProverInitArgs { } } - fn get_cloud_type_with_prompt(&self) -> CloudConnectionMode { + fn get_cloud_type_with_prompt(&self) -> InternalCloudConnectionMode { if self.dev { - return CloudConnectionMode::Local; + return InternalCloudConnectionMode::Local; } - let cloud_type = self.cloud_type.clone().unwrap_or_else(|| { + self.cloud_type.clone().unwrap_or_else(|| { PromptSelect::new( MSG_CLOUD_TYPE_PROMPT, InternalCloudConnectionMode::iter().rev(), ) .ask() - }); - - cloud_type.into() + }) } fn fill_database_values_with_prompt( diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs b/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs index 88eec0688da7..77bb45a1336d 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/compressor_keys.rs @@ -1,12 +1,13 @@ +use std::path::{Path, PathBuf}; + use anyhow::Context; use xshell::Shell; use zkstack_cli_common::{logger, spinner::Spinner}; -use zkstack_cli_config::{get_link_to_prover, EcosystemConfig, GeneralConfig}; +use zkstack_cli_config::{get_link_to_prover, raw::PatchedConfig, EcosystemConfig}; use super::args::compressor_keys::CompressorKeysArgs; use crate::messages::{ - MSG_CHAIN_NOT_FOUND_ERR, MSG_DOWNLOADING_SETUP_COMPRESSOR_KEY_SPINNER, - MSG_PROOF_COMPRESSOR_CONFIG_NOT_FOUND_ERR, MSG_SETUP_KEY_PATH_ERROR, + MSG_CHAIN_NOT_FOUND_ERR, MSG_DOWNLOADING_SETUP_COMPRESSOR_KEY_SPINNER, MSG_SETUP_KEY_PATH_ERROR, }; pub(crate) async fn run(shell: &Shell, args: CompressorKeysArgs) -> anyhow::Result<()> { @@ -14,7 +15,7 @@ pub(crate) async fn run(shell: &Shell, args: CompressorKeysArgs) -> anyhow::Resu let chain_config = ecosystem_config .load_current_chain() .context(MSG_CHAIN_NOT_FOUND_ERR)?; - let mut general_config = chain_config.get_general_config()?; + let mut general_config = chain_config.get_general_config().await?.patched(); let default_path = get_default_compressor_keys_path(&ecosystem_config)?; let args = args.fill_values_with_prompt(&default_path); @@ -23,41 +24,29 @@ pub(crate) async fn run(shell: &Shell, args: CompressorKeysArgs) -> anyhow::Resu download_compressor_key(shell, &mut general_config, &path)?; - chain_config.save_general_config(&general_config)?; - + general_config.save().await?; Ok(()) } pub(crate) fn download_compressor_key( shell: &Shell, - general_config: &mut GeneralConfig, - path: &str, + general_config: &mut PatchedConfig, + path: &Path, ) -> anyhow::Result<()> { let spinner = Spinner::new(MSG_DOWNLOADING_SETUP_COMPRESSOR_KEY_SPINNER); - let mut compressor_config: zksync_config::configs::FriProofCompressorConfig = general_config - .proof_compressor_config - .as_ref() - .expect(MSG_PROOF_COMPRESSOR_CONFIG_NOT_FOUND_ERR) - .clone(); - compressor_config.universal_setup_path = path.to_string(); - general_config.proof_compressor_config = Some(compressor_config.clone()); + general_config.insert_path("proof_compressor.universal_setup_path", path)?; - let path = std::path::Path::new(path); - - logger::info(format!( - "Downloading setup key by URL: {}", - compressor_config.universal_setup_download_url - )); + let url = general_config + .base() + .get::("proof_compressor.universal_setup_download_url")?; + logger::info(format!("Downloading setup key by URL: {url}")); let client = reqwest::blocking::Client::builder() .timeout(std::time::Duration::from_secs(600)) .build()?; - let response = client - .get(compressor_config.universal_setup_download_url) - .send()? - .bytes()?; + let response = client.get(url).send()?.bytes()?; shell.write_file(path, &response)?; spinner.finish(); @@ -66,10 +55,7 @@ pub(crate) fn download_compressor_key( pub fn get_default_compressor_keys_path( ecosystem_config: &EcosystemConfig, -) -> anyhow::Result { +) -> anyhow::Result { let link_to_prover = get_link_to_prover(ecosystem_config); - let path = link_to_prover.join("keys/setup/setup_compact.key"); - let string = path.to_str().unwrap(); - - Ok(String::from(string)) + Ok(link_to_prover.join("keys/setup/setup_compact.key")) } diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/init.rs b/zkstack_cli/crates/zkstack/src/commands/prover/init.rs index 51034e02a213..edfdc31e6186 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/init.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/init.rs @@ -10,28 +10,23 @@ use zkstack_cli_common::{ spinner::Spinner, }; use zkstack_cli_config::{ - copy_configs, get_link_to_prover, set_prover_database, traits::SaveConfigWithBasePath, - EcosystemConfig, + copy_configs, get_link_to_prover, raw::PatchedConfig, set_prover_database, EcosystemConfig, }; use zksync_config::{configs::object_store::ObjectStoreMode, ObjectStoreConfig}; use super::{ - args::init::{ProofStorageConfig, ProverInitArgs}, - compressor_keys::download_compressor_key, + args::init::{ProofStorageConfig, ProofStorageFileBacked, ProverInitArgs}, + compressor_keys::{download_compressor_key, get_default_compressor_keys_path}, gcs::create_gcs_bucket, init_bellman_cuda::run as init_bellman_cuda, setup_keys, }; use crate::{ - commands::prover::{ - args::init::ProofStorageFileBacked, compressor_keys::get_default_compressor_keys_path, - }, consts::{PROVER_MIGRATIONS, PROVER_STORE_MAX_RETRIES}, messages::{ MSG_CHAIN_NOT_FOUND_ERR, MSG_FAILED_TO_DROP_PROVER_DATABASE_ERR, - MSG_GENERAL_CONFIG_NOT_FOUND_ERR, MSG_INITIALIZING_DATABASES_SPINNER, - MSG_INITIALIZING_PROVER_DATABASE, MSG_PROVER_CONFIG_NOT_FOUND_ERR, MSG_PROVER_INITIALIZED, - MSG_SETUP_KEY_PATH_ERROR, + MSG_INITIALIZING_DATABASES_SPINNER, MSG_INITIALIZING_PROVER_DATABASE, + MSG_PROVER_INITIALIZED, MSG_SETUP_KEY_PATH_ERROR, }, }; @@ -45,15 +40,16 @@ pub(crate) async fn run(args: ProverInitArgs, shell: &Shell) -> anyhow::Result<( .context(MSG_CHAIN_NOT_FOUND_ERR)?; let args = args.fill_values_with_prompt(shell, &default_compressor_key_path, &chain_config)?; - if chain_config.get_general_config().is_err() || chain_config.get_secrets_config().is_err() { + if chain_config.get_general_config().await.is_err() + || chain_config.get_secrets_config().await.is_err() + { copy_configs(shell, &ecosystem_config.link_to_code, &chain_config.configs)?; } - let mut general_config = chain_config - .get_general_config() - .context(MSG_GENERAL_CONFIG_NOT_FOUND_ERR)?; + let mut general_config = chain_config.get_general_config().await?.patched(); - let proof_object_store_config = get_object_store_config(shell, Some(args.proof_store))?; + let proof_object_store_config = + get_object_store_config(shell, Some(args.proof_store))?.unwrap(); let public_object_store_config = get_object_store_config(shell, args.public_store)?; if let Some(args) = args.compressor_key_args { @@ -66,22 +62,23 @@ pub(crate) async fn run(args: ProverInitArgs, shell: &Shell) -> anyhow::Result<( setup_keys::run(args, shell).await?; } - let mut prover_config = general_config - .prover_config - .expect(MSG_PROVER_CONFIG_NOT_FOUND_ERR); - prover_config - .prover_object_store - .clone_from(&proof_object_store_config); + set_object_store( + &mut general_config, + "prover.prover_object_store", + &proof_object_store_config, + )?; if let Some(public_object_store_config) = public_object_store_config { - prover_config.shall_save_to_public_bucket = true; - prover_config.public_object_store = Some(public_object_store_config); + general_config.insert("prover.shall_save_to_public_bucket", true)?; + set_object_store( + &mut general_config, + "prover.public_object_store", + &public_object_store_config, + )?; } else { - prover_config.shall_save_to_public_bucket = false; + general_config.insert("prover.shall_save_to_public_bucket", false)?; } - prover_config.cloud_type = args.cloud_type; - general_config.prover_config = Some(prover_config); - - chain_config.save_general_config(&general_config)?; + general_config.insert_yaml("prover.cloud_type", args.cloud_type)?; + general_config.save().await?; if let Some(args) = args.bellman_cuda_config { init_bellman_cuda(shell, args).await?; @@ -90,9 +87,9 @@ pub(crate) async fn run(args: ProverInitArgs, shell: &Shell) -> anyhow::Result<( if let Some(prover_db) = &args.database_config { let spinner = Spinner::new(MSG_INITIALIZING_DATABASES_SPINNER); - let mut secrets = chain_config.get_secrets_config()?; + let mut secrets = chain_config.get_secrets_config().await?.patched(); set_prover_database(&mut secrets, &prover_db.database_config)?; - secrets.save_with_base_path(shell, &chain_config.configs)?; + secrets.save().await?; initialize_prover_database( shell, &prover_db.database_config, @@ -135,6 +132,50 @@ fn get_object_store_config( Ok(object_store) } +fn set_object_store( + patch: &mut PatchedConfig, + prefix: &str, + config: &ObjectStoreConfig, +) -> anyhow::Result<()> { + patch.insert(&format!("{prefix}.max_retries"), config.max_retries)?; + match &config.mode { + ObjectStoreMode::FileBacked { + file_backed_base_path, + } => { + patch.insert_yaml( + &format!("{prefix}.file_backed.file_backed_base_path"), + file_backed_base_path, + )?; + } + ObjectStoreMode::GCS { bucket_base_url } => { + patch.insert( + &format!("{prefix}.gcs.bucket_base_url"), + bucket_base_url.clone(), + )?; + } + ObjectStoreMode::GCSWithCredentialFile { + bucket_base_url, + gcs_credential_file_path, + } => { + patch.insert( + &format!("{prefix}.gcs_with_credential_file.bucket_base_url"), + bucket_base_url.clone(), + )?; + patch.insert( + &format!("{prefix}.gcs_with_credential_file.gcs_credential_file_path"), + gcs_credential_file_path.clone(), + )?; + } + ObjectStoreMode::GCSAnonymousReadOnly { bucket_base_url } => { + patch.insert( + &format!("{prefix}.gcs_anonymous_read_only.bucket_base_url"), + bucket_base_url.clone(), + )?; + } + } + Ok(()) +} + async fn initialize_prover_database( shell: &Shell, prover_db_config: &DatabaseConfig, diff --git a/zkstack_cli/crates/zkstack/src/commands/prover/run.rs b/zkstack_cli/crates/zkstack/src/commands/prover/run.rs index 495c41ef8255..e7101d92ab6e 100644 --- a/zkstack_cli/crates/zkstack/src/commands/prover/run.rs +++ b/zkstack_cli/crates/zkstack/src/commands/prover/run.rs @@ -79,7 +79,7 @@ pub(crate) async fn run(args: ProverRunArgs, shell: &Shell) -> anyhow::Result<() if in_docker { let path_to_configs = chain.configs.clone(); let path_to_prover = get_link_to_prover(&ecosystem_config); - update_setup_data_path(&chain, "prover/data/keys".to_string())?; + update_setup_data_path(&chain, "prover/data/keys").await?; run_dockerized_component( shell, component.image_name(), @@ -93,7 +93,7 @@ pub(crate) async fn run(args: ProverRunArgs, shell: &Shell) -> anyhow::Result<() &path_to_ecosystem, )? } else { - update_setup_data_path(&chain, "data/keys".to_string())?; + update_setup_data_path(&chain, "data/keys").await?; run_binary_component( shell, component.binary_name(), @@ -151,13 +151,8 @@ fn run_binary_component( cmd.run().context(error) } -fn update_setup_data_path(chain: &ChainConfig, path: String) -> anyhow::Result<()> { - let mut general_config = chain.get_general_config()?; - general_config - .prover_config - .as_mut() - .expect("Prover config not found") - .setup_data_path = path; - chain.save_general_config(&general_config)?; - Ok(()) +async fn update_setup_data_path(chain: &ChainConfig, path: &str) -> anyhow::Result<()> { + let mut general_config = chain.get_general_config().await?.patched(); + general_config.insert_path("prover.setup_data_path", path.as_ref())?; + general_config.save().await } diff --git a/zkstack_cli/crates/zkstack/src/commands/server.rs b/zkstack_cli/crates/zkstack/src/commands/server.rs index e1e4ca3ff99d..11b10ecec594 100644 --- a/zkstack_cli/crates/zkstack/src/commands/server.rs +++ b/zkstack_cli/crates/zkstack/src/commands/server.rs @@ -8,7 +8,7 @@ use zkstack_cli_common::{ }; use zkstack_cli_config::{ traits::FileConfigWithDefaultName, ChainConfig, ContractsConfig, EcosystemConfig, - GeneralConfig, GenesisConfig, SecretsConfig, WalletsConfig, + WalletsConfig, GENERAL_FILE, GENESIS_FILE, SECRETS_FILE, }; use zksync_config::configs::gateway::GatewayChainConfig; @@ -78,10 +78,10 @@ fn run_server( .run( shell, mode, - GenesisConfig::get_path_with_base_path(&chain_config.configs), + chain_config.configs.join(GENESIS_FILE), WalletsConfig::get_path_with_base_path(&chain_config.configs), - GeneralConfig::get_path_with_base_path(&chain_config.configs), - SecretsConfig::get_path_with_base_path(&chain_config.configs), + chain_config.configs.join(GENERAL_FILE), + chain_config.configs.join(SECRETS_FILE), ContractsConfig::get_path_with_base_path(&chain_config.configs), gateway_contracts, vec![], @@ -93,13 +93,9 @@ async fn wait_for_server(args: WaitArgs, chain_config: &ChainConfig) -> anyhow:: let verbose = global_config().verbose; let health_check_port = chain_config - .get_general_config()? - .api_config - .as_ref() - .context("no API config")? - .healthcheck - .port; - + .get_general_config() + .await? + .get("api.healthcheck.port")?; logger::info(MSG_WAITING_FOR_SERVER); args.poll_health_check(health_check_port, verbose).await?; logger::info(msg_waiting_for_server_success(health_check_port)); diff --git a/zkstack_cli/crates/zkstack/src/commands/update.rs b/zkstack_cli/crates/zkstack/src/commands/update.rs index 0e1d385f8fef..095619b43f0e 100644 --- a/zkstack_cli/crates/zkstack/src/commands/update.rs +++ b/zkstack_cli/crates/zkstack/src/commands/update.rs @@ -1,6 +1,7 @@ use std::path::Path; -use anyhow::{Context, Ok}; +use anyhow::Context; +use url::Url; use xshell::Shell; use zkstack_cli_common::{ db::migrate_db, @@ -182,17 +183,14 @@ async fn update_chain( )?; } - let secrets = chain.get_secrets_config()?; - - if let Some(db) = secrets.database { - if let Some(url) = db.server_url { - let path_to_migration = chain.link_to_code.join(SERVER_MIGRATIONS); - migrate_db(shell, path_to_migration, url.expose_url()).await?; - } - if let Some(url) = db.prover_url { - let path_to_migration = chain.link_to_code.join(PROVER_MIGRATIONS); - migrate_db(shell, path_to_migration, url.expose_url()).await?; - } + let secrets = chain.get_secrets_config().await?; + if let Some(url) = secrets.get_opt::("database.server_url")? { + let path_to_migration = chain.link_to_code.join(SERVER_MIGRATIONS); + migrate_db(shell, path_to_migration, &url).await?; + } + if let Some(url) = secrets.get_opt::("database.prover_url")? { + let path_to_migration = chain.link_to_code.join(PROVER_MIGRATIONS); + migrate_db(shell, path_to_migration, &url).await?; } Ok(()) } diff --git a/zkstack_cli/crates/zkstack/src/external_node.rs b/zkstack_cli/crates/zkstack/src/external_node.rs index 21d4e0db5592..7cc4dcbe6d68 100644 --- a/zkstack_cli/crates/zkstack/src/external_node.rs +++ b/zkstack_cli/crates/zkstack/src/external_node.rs @@ -3,10 +3,8 @@ use std::path::PathBuf; use anyhow::Context; use xshell::Shell; use zkstack_cli_config::{ - external_node::ENConfig, traits::FileConfigWithDefaultName, ChainConfig, GeneralConfig, - SecretsConfig, + ChainConfig, CONSENSUS_CONFIG_FILE, EN_CONFIG_FILE, GENERAL_FILE, SECRETS_FILE, }; -use zksync_config::configs::consensus::ConsensusConfig; use crate::messages::MSG_FAILED_TO_RUN_SERVER_ERR; @@ -28,17 +26,17 @@ impl RunExternalNode { .external_node_config_path .clone() .context("External node is not initialized")?; - let general_config = GeneralConfig::get_path_with_base_path(&en_path); - let secrets = SecretsConfig::get_path_with_base_path(&en_path); - let enconfig = ENConfig::get_path_with_base_path(&en_path); - let consensus_config = ConsensusConfig::get_path_with_base_path(&en_path); + let general_config = en_path.join(GENERAL_FILE); + let secrets = en_path.join(SECRETS_FILE); + let en_config = en_path.join(EN_CONFIG_FILE); + let consensus_config = en_path.join(CONSENSUS_CONFIG_FILE); Ok(Self { components, code_path: chain_config.link_to_code.clone(), general_config, secrets, - en_config: enconfig, + en_config, consensus_config, }) } diff --git a/zkstack_cli/crates/zkstack/src/messages.rs b/zkstack_cli/crates/zkstack/src/messages.rs index 179f7100ef9e..4a2cbc950b44 100644 --- a/zkstack_cli/crates/zkstack/src/messages.rs +++ b/zkstack_cli/crates/zkstack/src/messages.rs @@ -197,8 +197,6 @@ pub(super) const MSG_EVM_EMULATOR_HASH_MISSING_ERR: &str = does not contain EVM emulator hash"; /// Chain genesis related messages -pub(super) const MSG_L1_SECRETS_MUST_BE_PRESENTED: &str = "L1 secret must be presented"; -pub(super) const MSG_DATABASE_MUST_BE_PRESENTED: &str = "Database secret must be presented"; pub(super) const MSG_SERVER_DB_URL_HELP: &str = "Server database url without database name"; pub(super) const MSG_SERVER_DB_NAME_HELP: &str = "Server database name"; pub(super) const MSG_PROVER_DB_URL_HELP: &str = "Prover database url without database name"; @@ -370,9 +368,6 @@ pub(super) fn msg_preparing_en_config_is_done(path: &Path) -> String { pub(super) const MSG_EXTERNAL_NODE_CONFIG_NOT_INITIALIZED: &str = "External node is not initialized"; -pub(super) const MSG_CONSENSUS_CONFIG_MISSING_ERR: &str = "Consensus config is missing"; -pub(super) const MSG_CONSENSUS_SECRETS_MISSING_ERR: &str = "Consensus secrets config is missing"; -pub(super) const MSG_CONSENSUS_SECRETS_NODE_KEY_MISSING_ERR: &str = "Consensus node key is missing"; pub(super) const MSG_BUILDING_EN: &str = "Building external node"; pub(super) const MSG_FAILED_TO_BUILD_EN_ERR: &str = "Failed to build external node"; @@ -413,8 +408,6 @@ pub(super) const MSG_PROOF_STORE_GCS_BUCKET_BASE_URL_ERR: &str = "Bucket base URL should start with gs://"; pub(super) const MSG_PROOF_STORE_GCS_CREDENTIALS_FILE_PROMPT: &str = "Provide the path to the GCS credentials file:"; -pub(super) const MSG_GENERAL_CONFIG_NOT_FOUND_ERR: &str = "General config not found"; -pub(super) const MSG_PROVER_CONFIG_NOT_FOUND_ERR: &str = "Prover config not found"; pub(super) const MSG_PROVER_INITIALIZED: &str = "Prover has been initialized successfully"; pub(super) const MSG_CREATE_GCS_BUCKET_PROMPT: &str = "Do you want to create a new GCS bucket?"; pub(super) const MSG_CREATE_GCS_BUCKET_PROJECT_ID_PROMPT: &str = "Select the project ID:"; @@ -422,8 +415,6 @@ pub(super) const MSG_CREATE_GCS_BUCKET_PROJECT_ID_NO_PROJECTS_PROMPT: &str = "Provide a project ID:"; pub(super) const MSG_CREATE_GCS_BUCKET_NAME_PROMTP: &str = "What do you want to name the bucket?"; pub(super) const MSG_CREATE_GCS_BUCKET_LOCATION_PROMPT: &str = "What location do you want to use? Find available locations at https://cloud.google.com/storage/docs/locations"; -pub(super) const MSG_PROOF_COMPRESSOR_CONFIG_NOT_FOUND_ERR: &str = - "Proof compressor config not found"; pub(super) const MSG_DOWNLOADING_SETUP_COMPRESSOR_KEY_SPINNER: &str = "Downloading compressor setup key..."; pub(super) const MSG_DOWNLOAD_SETUP_COMPRESSOR_KEY_PROMPT: &str = @@ -569,7 +560,6 @@ pub(super) fn msg_updating_chain(chain: &str) -> String { pub(super) const MSG_RECEIPT_MISSING: &str = "receipt missing"; pub(super) const MSG_STATUS_MISSING: &str = "status missing"; pub(super) const MSG_TRANSACTION_FAILED: &str = "transaction failed"; -pub(super) const MSG_API_CONFIG_MISSING: &str = "api config missing"; pub(super) const MSG_MULTICALL3_CONTRACT_NOT_CONFIGURED: &str = "multicall3 contract not configured"; pub(super) const MSG_GOVERNOR_PRIVATE_KEY_NOT_SET: &str = "governor private key not set"; diff --git a/zkstack_cli/crates/zkstack/src/utils/consensus.rs b/zkstack_cli/crates/zkstack/src/utils/consensus.rs index 0a1287067434..7e4273e4c253 100644 --- a/zkstack_cli/crates/zkstack/src/utils/consensus.rs +++ b/zkstack_cli/crates/zkstack/src/utils/consensus.rs @@ -1,31 +1,9 @@ use anyhow::Context as _; -use secrecy::{ExposeSecret, Secret}; -use zkstack_cli_config::ChainConfig; -use zksync_config::configs::consensus::{ - AttesterPublicKey, AttesterSecretKey, ConsensusSecrets, GenesisSpec, NodePublicKey, - NodeSecretKey, ProtocolVersion, ValidatorPublicKey, ValidatorSecretKey, WeightedAttester, - WeightedValidator, -}; +use serde::{Deserialize, Serialize}; +use zkstack_cli_config::{raw::PatchedConfig, ChainConfig}; use zksync_consensus_crypto::{Text, TextFmt}; use zksync_consensus_roles::{attester, node, validator}; -pub(crate) fn parse_attester_committee( - attesters: &[WeightedAttester], -) -> anyhow::Result { - let attesters: Vec<_> = attesters - .iter() - .enumerate() - .map(|(i, v)| { - Ok(attester::WeightedAttester { - key: Text::new(&v.key.0).decode().context("key").context(i)?, - weight: v.weight, - }) - }) - .collect::>() - .context("attesters")?; - attester::Committee::new(attesters).context("Committee::new()") -} - #[derive(Debug, Clone)] pub struct ConsensusSecretKeys { validator_key: validator::SecretKey, @@ -53,56 +31,93 @@ fn get_consensus_public_keys(consensus_keys: &ConsensusSecretKeys) -> ConsensusP } } -pub fn get_genesis_specs( +/// Mirrors key–address pair used in the consensus config. +#[derive(Debug, Serialize)] +pub(crate) struct KeyAndAddress { + pub key: String, + pub addr: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Weighted { + key: String, + weight: u64, +} + +impl Weighted { + fn new(key: String, weight: u64) -> Self { + Self { key, weight } + } +} + +pub(crate) fn read_attester_committee_yaml( + raw_yaml: serde_yaml::Value, +) -> anyhow::Result { + #[derive(Debug, Deserialize)] + struct SetAttesterCommitteeFile { + attesters: Vec, + } + + let file: SetAttesterCommitteeFile = + serde_yaml::from_value(raw_yaml).context("invalid attester committee format")?; + let attesters: Vec<_> = file + .attesters + .iter() + .enumerate() + .map(|(i, v)| { + Ok(attester::WeightedAttester { + key: Text::new(&v.key).decode().context("key").context(i)?, + weight: v.weight, + }) + }) + .collect::>() + .context("attesters")?; + attester::Committee::new(attesters).context("Committee::new()") +} + +pub fn set_genesis_specs( + general: &mut PatchedConfig, chain_config: &ChainConfig, consensus_keys: &ConsensusSecretKeys, -) -> GenesisSpec { +) -> anyhow::Result<()> { let public_keys = get_consensus_public_keys(consensus_keys); let validator_key = public_keys.validator_key.encode(); let attester_key = public_keys.attester_key.encode(); + let leader = validator_key.clone(); - let validator = WeightedValidator { - key: ValidatorPublicKey(validator_key.clone()), - weight: 1, - }; - let attester = WeightedAttester { - key: AttesterPublicKey(attester_key), - weight: 1, - }; - let leader = ValidatorPublicKey(validator_key); - - GenesisSpec { - chain_id: chain_config.chain_id, - protocol_version: ProtocolVersion(1), - validators: vec![validator], - attesters: vec![attester], - leader, - registry_address: None, - seed_peers: [].into(), - } + general.insert( + "consensus.genesis_spec.chain_id", + chain_config.chain_id.as_u64(), + )?; + general.insert("consensus.genesis_spec.protocol_version", 1_u64)?; + general.insert_yaml( + "consensus.genesis_spec.validators", + [Weighted::new(validator_key, 1)], + )?; + general.insert_yaml( + "consensus.genesis_spec.attesters", + [Weighted::new(attester_key, 1)], + )?; + general.insert("consensus.genesis_spec.leader", leader)?; + Ok(()) } -pub fn get_consensus_secrets(consensus_keys: &ConsensusSecretKeys) -> ConsensusSecrets { +pub(crate) fn set_consensus_secrets( + secrets: &mut PatchedConfig, + consensus_keys: &ConsensusSecretKeys, +) -> anyhow::Result<()> { let validator_key = consensus_keys.validator_key.encode(); let attester_key = consensus_keys.attester_key.encode(); let node_key = consensus_keys.node_key.encode(); - - ConsensusSecrets { - validator_key: Some(ValidatorSecretKey(Secret::new(validator_key))), - attester_key: Some(AttesterSecretKey(Secret::new(attester_key))), - node_key: Some(NodeSecretKey(Secret::new(node_key))), - } -} - -pub fn node_public_key(secrets: &ConsensusSecrets) -> anyhow::Result> { - Ok(node_key(secrets)?.map(|node_secret_key| NodePublicKey(node_secret_key.public().encode()))) -} -fn node_key(secrets: &ConsensusSecrets) -> anyhow::Result> { - read_secret_text(secrets.node_key.as_ref().map(|x| &x.0)) + secrets.insert("consensus.validator_key", validator_key)?; + secrets.insert("consensus.attester_key", attester_key)?; + secrets.insert("consensus.node_key", node_key)?; + Ok(()) } -fn read_secret_text(text: Option<&Secret>) -> anyhow::Result> { - text.map(|text| Text::new(text.expose_secret()).decode()) - .transpose() - .map_err(|_| anyhow::format_err!("invalid format")) +pub fn node_public_key(secret_key: &str) -> anyhow::Result { + let secret_key: node::SecretKey = Text::new(secret_key) + .decode() + .context("invalid node key format")?; + Ok(secret_key.public().encode()) } From b04e22657de545530f7f44111d472bff49c77e83 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:37:32 +0200 Subject: [PATCH 41/52] chore: do not drain transient storage in vm (#3537) 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`. --- .../src/versions/vm_latest/old_vm/history_recorder.rs | 8 ++++++-- .../lib/multivm/src/versions/vm_latest/oracles/storage.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs index 9dac6480dc57..7e16382861c5 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs @@ -820,8 +820,12 @@ impl HistoryRecorder { self.apply_historic_record(StorageHistoryRecord { key, value }, timestamp) } - pub(crate) fn drain_inner(&mut self) -> Vec<(StorageKey, U256)> { - self.inner.inner.drain().collect() + pub(crate) fn clone_vec(&mut self) -> Vec<(StorageKey, U256)> { + self.inner + .inner + .iter() + .map(|(key, value)| (*key, *value)) + .collect() } } diff --git a/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs b/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs index 242cdc6a2239..5c8fe1d2a97f 100644 --- a/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs +++ b/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs @@ -576,7 +576,7 @@ impl VmStorageOracle for StorageOracle { // Note that while the history is preserved, the inner parts are fully cleared out. // TODO(X): potentially optimize this function by allowing rollbacks only at the bounds of transactions. - let current_active_keys = self.transient_storage.drain_inner(); + let current_active_keys = self.transient_storage.clone_vec(); for (key, current_value) in current_active_keys { self.write_transient_storage_value(ReducedTstoreLogQuery { // We currently only support rollup shard id From 512dd459307e57762dd4cc2c78ff4151634b6941 Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:00:19 +0200 Subject: [PATCH 42/52] fix: add . to readme (#3538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ dummy change to trigger release-please PR ## 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/bin/external_node/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/bin/external_node/README.md b/core/bin/external_node/README.md index 335ceed7b719..658dfe157068 100644 --- a/core/bin/external_node/README.md +++ b/core/bin/external_node/README.md @@ -6,7 +6,7 @@ Note: this README is under construction. ## Local development -This section describes how to run the external node locally +This section describes how to run the external node locally. ### Configuration From 0a096819a585f37510c6f01ac3b0aefd8edbe2f0 Mon Sep 17 00:00:00 2001 From: Roman Brodetski Date: Tue, 28 Jan 2025 21:01:01 +0700 Subject: [PATCH 43/52] fix(alerts): Alert if there was no successful eth-watcher iterations for 5 minutes (#3299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have [this](https://github.com/matter-labs/gitops-kubernetes/blob/main/apps/environments/raas-abstract-testnet/server-v2/server-v2-core.yaml#L185) alert: ``` no_eth_watch_iterations: promql: max(rate(server_eth_watch_eth_poll[30s])) compare: "==" thresholds: critical: 0 duration: 5m annotations: description: "No ETH watch iterations for 5 minutes" summary: "No ETH watch iterations for 5 minutes" runbook_url: "https://www.notion.so/matterlabs/On-Call-1d597d29c9b64c08919bf53c952cd03e?pvs=4#78da3253ab3e41aa8175e72caf456c86" ``` But we reported this `server_eth_watch_eth_poll` metric on every iteration - even if it wasn't successful. This PR changes the logic so that it's only reported when successful - meaning that we will be alerted if there was no successful iterations for five minutes. We may want to increase the threshold to something like 10 minutes to avoid being notified at transient L1 provider issues. Co-authored-by: Tomasz Grześkiewicz --- core/node/eth_watch/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/node/eth_watch/src/lib.rs b/core/node/eth_watch/src/lib.rs index f866c8e627c5..d5590159046c 100644 --- a/core/node/eth_watch/src/lib.rs +++ b/core/node/eth_watch/src/lib.rs @@ -150,11 +150,13 @@ impl EthWatch { _ = timer.tick() => { /* continue iterations */ } _ = stop_receiver.changed() => break, } - METRICS.eth_poll.inc(); let mut storage = pool.connection_tagged("eth_watch").await?; match self.loop_iteration(&mut storage).await { - Ok(()) => { /* everything went fine */ } + Ok(()) => { + /* everything went fine */ + METRICS.eth_poll.inc(); + } Err(EventProcessorError::Internal(err)) => { tracing::error!("Internal error processing new blocks: {err:?}"); return Err(err); From b7ee3660062ec13fc515acf390fe7c06af537075 Mon Sep 17 00:00:00 2001 From: zksync-era-bot <147085853+zksync-era-bot@users.noreply.github.com> Date: Tue, 28 Jan 2025 18:01:29 +0400 Subject: [PATCH 44/52] chore(main): release core 26.2.1 (#3539) :robot: I have created a release *beep* *boop* --- ## [26.2.1](https://github.com/matter-labs/zksync-era/compare/core-v26.2.0...core-v26.2.1) (2025-01-28) ### Bug Fixes * add . to readme ([#3538](https://github.com/matter-labs/zksync-era/issues/3538)) ([512dd45](https://github.com/matter-labs/zksync-era/commit/512dd459307e57762dd4cc2c78ff4151634b6941)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: zksync-era-bot Co-authored-by: perekopskiy --- .github/release-please/manifest.json | 2 +- core/CHANGELOG.md | 7 ++ core/Cargo.lock | 156 +++++++++++++-------------- core/Cargo.toml | 130 +++++++++++----------- prover/Cargo.lock | 46 ++++---- zkstack_cli/Cargo.lock | 24 ++--- 6 files changed, 186 insertions(+), 179 deletions(-) diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index 2ebd4f487587..c64c1f4ecc4f 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,5 +1,5 @@ { - "core": "26.2.0", + "core": "26.2.1", "prover": "18.0.0", "zkstack_cli": "0.1.2" } diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 0354b180b0bb..42ddd01a6775 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [26.2.1](https://github.com/matter-labs/zksync-era/compare/core-v26.2.0...core-v26.2.1) (2025-01-28) + + +### Bug Fixes + +* add . to readme ([#3538](https://github.com/matter-labs/zksync-era/issues/3538)) ([512dd45](https://github.com/matter-labs/zksync-era/commit/512dd459307e57762dd4cc2c78ff4151634b6941)) + ## [26.2.0](https://github.com/matter-labs/zksync-era/compare/core-v26.1.0...core-v26.2.0) (2025-01-24) diff --git a/core/Cargo.lock b/core/Cargo.lock index 7c59753d45bc..f90bc00bb68d 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1233,7 +1233,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "block_reverter" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -2233,7 +2233,7 @@ dependencies = [ [[package]] name = "custom_genesis_export" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -3485,7 +3485,7 @@ dependencies = [ [[package]] name = "genesis_generator" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -5156,7 +5156,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "loadnext" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -5343,7 +5343,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "merkle_tree_consistency_checker" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -7984,7 +7984,7 @@ dependencies = [ [[package]] name = "selector_generator" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -8644,7 +8644,7 @@ dependencies = [ [[package]] name = "snapshots_creator" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "futures 0.3.31", @@ -9393,7 +9393,7 @@ dependencies = [ [[package]] name = "system-constants-generator" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "codegen", "once_cell", @@ -10373,7 +10373,7 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "verified_sources_fetcher" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "serde_json", @@ -10430,7 +10430,7 @@ dependencies = [ [[package]] name = "vm-benchmark" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "assert_matches", "criterion", @@ -11293,7 +11293,7 @@ dependencies = [ [[package]] name = "zksync_base_token_adjuster" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11315,7 +11315,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -11360,7 +11360,7 @@ dependencies = [ [[package]] name = "zksync_block_reverter" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11384,7 +11384,7 @@ dependencies = [ [[package]] name = "zksync_circuit_breaker" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11398,7 +11398,7 @@ dependencies = [ [[package]] name = "zksync_commitment_generator" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "circuit_encodings", @@ -11446,7 +11446,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -11619,7 +11619,7 @@ dependencies = [ [[package]] name = "zksync_consistency_checker" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11644,7 +11644,7 @@ dependencies = [ [[package]] name = "zksync_contract_verification_server" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "axum 0.7.9", @@ -11663,7 +11663,7 @@ dependencies = [ [[package]] name = "zksync_contract_verifier" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -11680,7 +11680,7 @@ dependencies = [ [[package]] name = "zksync_contract_verifier_lib" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11712,7 +11712,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "bincode", "envy", @@ -11726,7 +11726,7 @@ dependencies = [ [[package]] name = "zksync_core_leftovers" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "ctrlc", @@ -11740,7 +11740,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "blake2 0.10.6", @@ -11768,7 +11768,7 @@ dependencies = [ [[package]] name = "zksync_da_client" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11777,7 +11777,7 @@ dependencies = [ [[package]] name = "zksync_da_clients" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -11821,7 +11821,7 @@ dependencies = [ [[package]] name = "zksync_da_dispatcher" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -11838,7 +11838,7 @@ dependencies = [ [[package]] name = "zksync_dal" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "bigdecimal", @@ -11874,7 +11874,7 @@ dependencies = [ [[package]] name = "zksync_db_connection" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11892,7 +11892,7 @@ dependencies = [ [[package]] name = "zksync_env_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "envy", @@ -11904,7 +11904,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "assert_matches", "async-trait", @@ -11926,7 +11926,7 @@ dependencies = [ [[package]] name = "zksync_eth_sender" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -11956,7 +11956,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -11967,7 +11967,7 @@ dependencies = [ [[package]] name = "zksync_eth_watch" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-recursion", @@ -11994,7 +11994,7 @@ dependencies = [ [[package]] name = "zksync_external_node" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12048,7 +12048,7 @@ dependencies = [ [[package]] name = "zksync_external_price_api" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12069,7 +12069,7 @@ dependencies = [ [[package]] name = "zksync_external_proof_integration_api" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12115,7 +12115,7 @@ dependencies = [ [[package]] name = "zksync_health_check" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "assert_matches", "async-trait", @@ -12130,7 +12130,7 @@ dependencies = [ [[package]] name = "zksync_house_keeper" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12162,7 +12162,7 @@ dependencies = [ [[package]] name = "zksync_l1_contract_interface" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "circuit_definitions", @@ -12183,7 +12183,7 @@ dependencies = [ [[package]] name = "zksync_logs_bloom_backfill" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "tokio", @@ -12195,7 +12195,7 @@ dependencies = [ [[package]] name = "zksync_mempool" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "tracing", "zksync_types", @@ -12203,7 +12203,7 @@ dependencies = [ [[package]] name = "zksync_merkle_tree" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12232,7 +12232,7 @@ dependencies = [ [[package]] name = "zksync_metadata_calculator" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12266,7 +12266,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "criterion", "once_cell", @@ -12276,7 +12276,7 @@ dependencies = [ [[package]] name = "zksync_multivm" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12308,7 +12308,7 @@ dependencies = [ [[package]] name = "zksync_node_api_server" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12362,7 +12362,7 @@ dependencies = [ [[package]] name = "zksync_node_consensus" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12406,7 +12406,7 @@ dependencies = [ [[package]] name = "zksync_node_db_pruner" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12428,7 +12428,7 @@ dependencies = [ [[package]] name = "zksync_node_fee_model" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12447,7 +12447,7 @@ dependencies = [ [[package]] name = "zksync_node_framework" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12510,7 +12510,7 @@ dependencies = [ [[package]] name = "zksync_node_framework_derive" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "proc-macro2 1.0.92", "quote 1.0.37", @@ -12519,7 +12519,7 @@ dependencies = [ [[package]] name = "zksync_node_genesis" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -12540,7 +12540,7 @@ dependencies = [ [[package]] name = "zksync_node_storage_init" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12562,7 +12562,7 @@ dependencies = [ [[package]] name = "zksync_node_sync" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12597,7 +12597,7 @@ dependencies = [ [[package]] name = "zksync_node_test_utils" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "zksync_contracts", "zksync_dal", @@ -12609,7 +12609,7 @@ dependencies = [ [[package]] name = "zksync_object_store" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12647,7 +12647,7 @@ dependencies = [ [[package]] name = "zksync_proof_data_handler" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "axum 0.7.9", @@ -12709,7 +12709,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -12729,7 +12729,7 @@ dependencies = [ [[package]] name = "zksync_prover_interface" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "bincode", "chrono", @@ -12749,7 +12749,7 @@ dependencies = [ [[package]] name = "zksync_queued_job_processor" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12761,7 +12761,7 @@ dependencies = [ [[package]] name = "zksync_reorg_detector" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12782,7 +12782,7 @@ dependencies = [ [[package]] name = "zksync_server" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "clap 4.5.23", @@ -12812,7 +12812,7 @@ dependencies = [ [[package]] name = "zksync_shared_metrics" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "rustc_version 0.4.1", "serde", @@ -12824,7 +12824,7 @@ dependencies = [ [[package]] name = "zksync_snapshots_applier" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12863,7 +12863,7 @@ dependencies = [ [[package]] name = "zksync_state" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12888,7 +12888,7 @@ dependencies = [ [[package]] name = "zksync_state_keeper" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -12928,7 +12928,7 @@ dependencies = [ [[package]] name = "zksync_storage" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "num_cpus", "once_cell", @@ -12941,7 +12941,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -12949,7 +12949,7 @@ dependencies = [ [[package]] name = "zksync_tee_prover" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -12974,7 +12974,7 @@ dependencies = [ [[package]] name = "zksync_tee_verifier" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "bincode", @@ -12992,7 +12992,7 @@ dependencies = [ [[package]] name = "zksync_test_contracts" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "ethabi", "foundry-compilers", @@ -13008,7 +13008,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13043,7 +13043,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13059,7 +13059,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -13104,7 +13104,7 @@ dependencies = [ [[package]] name = "zksync_vm_executor" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13122,7 +13122,7 @@ dependencies = [ [[package]] name = "zksync_vm_interface" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13140,7 +13140,7 @@ dependencies = [ [[package]] name = "zksync_vm_runner" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", @@ -13174,7 +13174,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "assert_matches", diff --git a/core/Cargo.toml b/core/Cargo.toml index 99665b2bab28..fca865414829 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -91,7 +91,7 @@ inherits = "release" debug = true [workspace.package] -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -258,70 +258,70 @@ zksync_protobuf = "=0.8.0" zksync_protobuf_build = "=0.8.0" # "Local" dependencies -zksync_multivm = { version = "26.2.0-non-semver-compat", path = "lib/multivm" } -zksync_vlog = { version = "26.2.0-non-semver-compat", path = "lib/vlog" } -zksync_vm_interface = { version = "26.2.0-non-semver-compat", path = "lib/vm_interface" } -zksync_vm_executor = { version = "26.2.0-non-semver-compat", path = "lib/vm_executor" } -zksync_basic_types = { version = "26.2.0-non-semver-compat", path = "lib/basic_types" } -zksync_circuit_breaker = { version = "26.2.0-non-semver-compat", path = "lib/circuit_breaker" } -zksync_config = { version = "26.2.0-non-semver-compat", path = "lib/config" } -zksync_contract_verifier_lib = { version = "26.2.0-non-semver-compat", path = "lib/contract_verifier" } -zksync_contracts = { version = "26.2.0-non-semver-compat", path = "lib/contracts" } -zksync_core_leftovers = { version = "26.2.0-non-semver-compat", path = "lib/zksync_core_leftovers" } -zksync_dal = { version = "26.2.0-non-semver-compat", path = "lib/dal" } -zksync_db_connection = { version = "26.2.0-non-semver-compat", path = "lib/db_connection" } -zksync_env_config = { version = "26.2.0-non-semver-compat", path = "lib/env_config" } -zksync_eth_client = { version = "26.2.0-non-semver-compat", path = "lib/eth_client" } -zksync_da_client = { version = "26.2.0-non-semver-compat", path = "lib/da_client" } -zksync_eth_signer = { version = "26.2.0-non-semver-compat", path = "lib/eth_signer" } -zksync_health_check = { version = "26.2.0-non-semver-compat", path = "lib/health_check" } -zksync_l1_contract_interface = { version = "26.2.0-non-semver-compat", path = "lib/l1_contract_interface" } -zksync_mempool = { version = "26.2.0-non-semver-compat", path = "lib/mempool" } -zksync_merkle_tree = { version = "26.2.0-non-semver-compat", path = "lib/merkle_tree" } +zksync_multivm = { version = "26.2.1-non-semver-compat", path = "lib/multivm" } +zksync_vlog = { version = "26.2.1-non-semver-compat", path = "lib/vlog" } +zksync_vm_interface = { version = "26.2.1-non-semver-compat", path = "lib/vm_interface" } +zksync_vm_executor = { version = "26.2.1-non-semver-compat", path = "lib/vm_executor" } +zksync_basic_types = { version = "26.2.1-non-semver-compat", path = "lib/basic_types" } +zksync_circuit_breaker = { version = "26.2.1-non-semver-compat", path = "lib/circuit_breaker" } +zksync_config = { version = "26.2.1-non-semver-compat", path = "lib/config" } +zksync_contract_verifier_lib = { version = "26.2.1-non-semver-compat", path = "lib/contract_verifier" } +zksync_contracts = { version = "26.2.1-non-semver-compat", path = "lib/contracts" } +zksync_core_leftovers = { version = "26.2.1-non-semver-compat", path = "lib/zksync_core_leftovers" } +zksync_dal = { version = "26.2.1-non-semver-compat", path = "lib/dal" } +zksync_db_connection = { version = "26.2.1-non-semver-compat", path = "lib/db_connection" } +zksync_env_config = { version = "26.2.1-non-semver-compat", path = "lib/env_config" } +zksync_eth_client = { version = "26.2.1-non-semver-compat", path = "lib/eth_client" } +zksync_da_client = { version = "26.2.1-non-semver-compat", path = "lib/da_client" } +zksync_eth_signer = { version = "26.2.1-non-semver-compat", path = "lib/eth_signer" } +zksync_health_check = { version = "26.2.1-non-semver-compat", path = "lib/health_check" } +zksync_l1_contract_interface = { version = "26.2.1-non-semver-compat", path = "lib/l1_contract_interface" } +zksync_mempool = { version = "26.2.1-non-semver-compat", path = "lib/mempool" } +zksync_merkle_tree = { version = "26.2.1-non-semver-compat", path = "lib/merkle_tree" } zksync_bin_metadata = { version = "=26.1.0-non-semver-compat", path = "lib/bin_metadata" } -zksync_mini_merkle_tree = { version = "26.2.0-non-semver-compat", path = "lib/mini_merkle_tree" } -zksync_object_store = { version = "26.2.0-non-semver-compat", path = "lib/object_store" } -zksync_protobuf_config = { version = "26.2.0-non-semver-compat", path = "lib/protobuf_config" } -zksync_prover_interface = { version = "26.2.0-non-semver-compat", path = "lib/prover_interface" } -zksync_queued_job_processor = { version = "26.2.0-non-semver-compat", path = "lib/queued_job_processor" } -zksync_snapshots_applier = { version = "26.2.0-non-semver-compat", path = "lib/snapshots_applier" } -zksync_state = { version = "26.2.0-non-semver-compat", path = "lib/state" } -zksync_storage = { version = "26.2.0-non-semver-compat", path = "lib/storage" } -zksync_system_constants = { version = "26.2.0-non-semver-compat", path = "lib/constants" } -zksync_tee_verifier = { version = "26.2.0-non-semver-compat", path = "lib/tee_verifier" } -zksync_test_contracts = { version = "26.2.0-non-semver-compat", path = "lib/test_contracts" } -zksync_types = { version = "26.2.0-non-semver-compat", path = "lib/types" } -zksync_utils = { version = "26.2.0-non-semver-compat", path = "lib/utils" } -zksync_web3_decl = { version = "26.2.0-non-semver-compat", path = "lib/web3_decl" } -zksync_crypto_primitives = { version = "26.2.0-non-semver-compat", path = "lib/crypto_primitives" } -zksync_external_price_api = { version = "26.2.0-non-semver-compat", path = "lib/external_price_api" } +zksync_mini_merkle_tree = { version = "26.2.1-non-semver-compat", path = "lib/mini_merkle_tree" } +zksync_object_store = { version = "26.2.1-non-semver-compat", path = "lib/object_store" } +zksync_protobuf_config = { version = "26.2.1-non-semver-compat", path = "lib/protobuf_config" } +zksync_prover_interface = { version = "26.2.1-non-semver-compat", path = "lib/prover_interface" } +zksync_queued_job_processor = { version = "26.2.1-non-semver-compat", path = "lib/queued_job_processor" } +zksync_snapshots_applier = { version = "26.2.1-non-semver-compat", path = "lib/snapshots_applier" } +zksync_state = { version = "26.2.1-non-semver-compat", path = "lib/state" } +zksync_storage = { version = "26.2.1-non-semver-compat", path = "lib/storage" } +zksync_system_constants = { version = "26.2.1-non-semver-compat", path = "lib/constants" } +zksync_tee_verifier = { version = "26.2.1-non-semver-compat", path = "lib/tee_verifier" } +zksync_test_contracts = { version = "26.2.1-non-semver-compat", path = "lib/test_contracts" } +zksync_types = { version = "26.2.1-non-semver-compat", path = "lib/types" } +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" } # Framework and components -zksync_node_framework = { version = "26.2.0-non-semver-compat", path = "node/node_framework" } -zksync_node_framework_derive = { version = "26.2.0-non-semver-compat", path = "lib/node_framework_derive" } -zksync_eth_watch = { version = "26.2.0-non-semver-compat", path = "node/eth_watch" } -zksync_shared_metrics = { version = "26.2.0-non-semver-compat", path = "node/shared_metrics" } -zksync_proof_data_handler = { version = "26.2.0-non-semver-compat", path = "node/proof_data_handler" } -zksync_block_reverter = { version = "26.2.0-non-semver-compat", path = "node/block_reverter" } -zksync_commitment_generator = { version = "26.2.0-non-semver-compat", path = "node/commitment_generator" } -zksync_house_keeper = { version = "26.2.0-non-semver-compat", path = "node/house_keeper" } -zksync_node_genesis = { version = "26.2.0-non-semver-compat", path = "node/genesis" } -zksync_da_dispatcher = { version = "26.2.0-non-semver-compat", path = "node/da_dispatcher" } -zksync_da_clients = { version = "26.2.0-non-semver-compat", path = "node/da_clients" } -zksync_eth_sender = { version = "26.2.0-non-semver-compat", path = "node/eth_sender" } -zksync_node_db_pruner = { version = "26.2.0-non-semver-compat", path = "node/db_pruner" } -zksync_node_fee_model = { version = "26.2.0-non-semver-compat", path = "node/fee_model" } -zksync_vm_runner = { version = "26.2.0-non-semver-compat", path = "node/vm_runner" } -zksync_external_proof_integration_api = { version = "26.2.0-non-semver-compat", path = "node/external_proof_integration_api" } -zksync_node_test_utils = { version = "26.2.0-non-semver-compat", path = "node/test_utils" } -zksync_state_keeper = { version = "26.2.0-non-semver-compat", path = "node/state_keeper" } -zksync_reorg_detector = { version = "26.2.0-non-semver-compat", path = "node/reorg_detector" } -zksync_consistency_checker = { version = "26.2.0-non-semver-compat", path = "node/consistency_checker" } -zksync_metadata_calculator = { version = "26.2.0-non-semver-compat", path = "node/metadata_calculator" } -zksync_node_sync = { version = "26.2.0-non-semver-compat", path = "node/node_sync" } -zksync_node_storage_init = { version = "26.2.0-non-semver-compat", path = "node/node_storage_init" } -zksync_node_consensus = { version = "26.2.0-non-semver-compat", path = "node/consensus" } -zksync_contract_verification_server = { version = "26.2.0-non-semver-compat", path = "node/contract_verification_server" } -zksync_node_api_server = { version = "26.2.0-non-semver-compat", path = "node/api_server" } -zksync_base_token_adjuster = { version = "26.2.0-non-semver-compat", path = "node/base_token_adjuster" } -zksync_logs_bloom_backfill = { version = "26.2.0-non-semver-compat", path = "node/logs_bloom_backfill" } +zksync_node_framework = { version = "26.2.1-non-semver-compat", path = "node/node_framework" } +zksync_node_framework_derive = { version = "26.2.1-non-semver-compat", path = "lib/node_framework_derive" } +zksync_eth_watch = { version = "26.2.1-non-semver-compat", path = "node/eth_watch" } +zksync_shared_metrics = { version = "26.2.1-non-semver-compat", path = "node/shared_metrics" } +zksync_proof_data_handler = { version = "26.2.1-non-semver-compat", path = "node/proof_data_handler" } +zksync_block_reverter = { version = "26.2.1-non-semver-compat", path = "node/block_reverter" } +zksync_commitment_generator = { version = "26.2.1-non-semver-compat", path = "node/commitment_generator" } +zksync_house_keeper = { version = "26.2.1-non-semver-compat", path = "node/house_keeper" } +zksync_node_genesis = { version = "26.2.1-non-semver-compat", path = "node/genesis" } +zksync_da_dispatcher = { version = "26.2.1-non-semver-compat", path = "node/da_dispatcher" } +zksync_da_clients = { version = "26.2.1-non-semver-compat", path = "node/da_clients" } +zksync_eth_sender = { version = "26.2.1-non-semver-compat", path = "node/eth_sender" } +zksync_node_db_pruner = { version = "26.2.1-non-semver-compat", path = "node/db_pruner" } +zksync_node_fee_model = { version = "26.2.1-non-semver-compat", path = "node/fee_model" } +zksync_vm_runner = { version = "26.2.1-non-semver-compat", path = "node/vm_runner" } +zksync_external_proof_integration_api = { version = "26.2.1-non-semver-compat", path = "node/external_proof_integration_api" } +zksync_node_test_utils = { version = "26.2.1-non-semver-compat", path = "node/test_utils" } +zksync_state_keeper = { version = "26.2.1-non-semver-compat", path = "node/state_keeper" } +zksync_reorg_detector = { version = "26.2.1-non-semver-compat", path = "node/reorg_detector" } +zksync_consistency_checker = { version = "26.2.1-non-semver-compat", path = "node/consistency_checker" } +zksync_metadata_calculator = { version = "26.2.1-non-semver-compat", path = "node/metadata_calculator" } +zksync_node_sync = { version = "26.2.1-non-semver-compat", path = "node/node_sync" } +zksync_node_storage_init = { version = "26.2.1-non-semver-compat", path = "node/node_storage_init" } +zksync_node_consensus = { version = "26.2.1-non-semver-compat", path = "node/consensus" } +zksync_contract_verification_server = { version = "26.2.1-non-semver-compat", path = "node/contract_verification_server" } +zksync_node_api_server = { version = "26.2.1-non-semver-compat", path = "node/api_server" } +zksync_base_token_adjuster = { version = "26.2.1-non-semver-compat", path = "node/base_token_adjuster" } +zksync_logs_bloom_backfill = { version = "26.2.1-non-semver-compat", path = "node/logs_bloom_backfill" } diff --git a/prover/Cargo.lock b/prover/Cargo.lock index bc33f8d54385..30e5bc46af77 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -8070,7 +8070,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -8183,7 +8183,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -8274,7 +8274,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "envy", "hex", @@ -8287,7 +8287,7 @@ dependencies = [ [[package]] name = "zksync_core_leftovers" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "ctrlc", @@ -8301,7 +8301,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "blake2 0.10.6", @@ -8329,7 +8329,7 @@ dependencies = [ [[package]] name = "zksync_dal" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "bigdecimal", @@ -8364,7 +8364,7 @@ dependencies = [ [[package]] name = "zksync_db_connection" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "rand 0.8.5", @@ -8380,7 +8380,7 @@ dependencies = [ [[package]] name = "zksync_env_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "envy", @@ -8391,7 +8391,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "async-trait", "jsonrpsee", @@ -8408,7 +8408,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -8464,7 +8464,7 @@ dependencies = [ [[package]] name = "zksync_l1_contract_interface" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "circuit_definitions", @@ -8481,7 +8481,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -8490,7 +8490,7 @@ dependencies = [ [[package]] name = "zksync_multivm" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "circuit_sequencer_api", @@ -8516,7 +8516,7 @@ dependencies = [ [[package]] name = "zksync_object_store" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8627,7 +8627,7 @@ dependencies = [ [[package]] name = "zksync_protobuf_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "hex", @@ -8782,7 +8782,7 @@ dependencies = [ [[package]] name = "zksync_prover_interface" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "chrono", "circuit_definitions", @@ -8864,7 +8864,7 @@ dependencies = [ [[package]] name = "zksync_queued_job_processor" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8893,7 +8893,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -8901,7 +8901,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -8933,7 +8933,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "futures 0.3.31", @@ -8972,7 +8972,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -9017,7 +9017,7 @@ dependencies = [ [[package]] name = "zksync_vm_interface" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -9033,7 +9033,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index f575dfcd4eab..cf60178cc347 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -7163,7 +7163,7 @@ dependencies = [ [[package]] name = "zksync_basic_types" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -7203,7 +7203,7 @@ dependencies = [ [[package]] name = "zksync_config" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "rand", @@ -7272,7 +7272,7 @@ dependencies = [ [[package]] name = "zksync_contracts" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "envy", "hex", @@ -7285,7 +7285,7 @@ dependencies = [ [[package]] name = "zksync_crypto_primitives" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "blake2", @@ -7301,7 +7301,7 @@ dependencies = [ [[package]] name = "zksync_eth_client" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "async-trait", "jsonrpsee", @@ -7318,7 +7318,7 @@ dependencies = [ [[package]] name = "zksync_eth_signer" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "async-trait", "rlp", @@ -7329,7 +7329,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -7376,7 +7376,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "once_cell", "zksync_basic_types", @@ -7384,7 +7384,7 @@ dependencies = [ [[package]] name = "zksync_types" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", @@ -7416,7 +7416,7 @@ dependencies = [ [[package]] name = "zksync_utils" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "futures", @@ -7431,7 +7431,7 @@ dependencies = [ [[package]] name = "zksync_vlog" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "chrono", @@ -7456,7 +7456,7 @@ dependencies = [ [[package]] name = "zksync_web3_decl" -version = "26.2.0-non-semver-compat" +version = "26.2.1-non-semver-compat" dependencies = [ "anyhow", "async-trait", From 416ea31edf1a11655f92735766415383c2e6c52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Grze=C5=9Bkiewicz?= Date: Tue, 28 Jan 2025 16:32:18 +0100 Subject: [PATCH 45/52] chore(en): better EN documentation and minor fixes (#3540) This PR contains EN improvements and fixes based on most often asked partner's questions --- core/node/api_server/src/execution_sandbox/mod.rs | 2 +- core/node/api_server/src/web3/namespaces/debug.rs | 10 ++++++++++ core/node/node_storage_init/src/lib.rs | 6 +++++- docs/src/guides/external-node/01_intro.md | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/node/api_server/src/execution_sandbox/mod.rs b/core/node/api_server/src/execution_sandbox/mod.rs index bcba200f5ebc..78bc85de9f22 100644 --- a/core/node/api_server/src/execution_sandbox/mod.rs +++ b/core/node/api_server/src/execution_sandbox/mod.rs @@ -274,7 +274,7 @@ impl BlockStartInfo { #[derive(Debug, thiserror::Error)] pub enum BlockArgsError { - #[error("Block is pruned; first retained block is {0}")] + #[error("Block is not available, either it was pruned or the node was started from a snapshot created later than this block; first retained block is {0}")] Pruned(L2BlockNumber), #[error("Block is missing, but can appear in the future")] Missing, diff --git a/core/node/api_server/src/web3/namespaces/debug.rs b/core/node/api_server/src/web3/namespaces/debug.rs index 180de6b273e5..a70367a858fb 100644 --- a/core/node/api_server/src/web3/namespaces/debug.rs +++ b/core/node/api_server/src/web3/namespaces/debug.rs @@ -174,6 +174,11 @@ impl DebugNamespace { } let mut connection = self.state.acquire_connection().await?; + self.state + .start_info + .ensure_not_pruned(block_id, &mut connection) + .await?; + let block_number = self.state.resolve_block(&mut connection, block_id).await?; // let block_hash = block_hash self.state. self.current_method() @@ -252,6 +257,11 @@ impl DebugNamespace { let options = options.unwrap_or_default(); let mut connection = self.state.acquire_connection().await?; + self.state + .start_info + .ensure_not_pruned(block_id, &mut connection) + .await?; + let block_args = self .state .resolve_block_args(&mut connection, block_id) diff --git a/core/node/node_storage_init/src/lib.rs b/core/node/node_storage_init/src/lib.rs index a8b72b769a18..f876fa446c4b 100644 --- a/core/node/node_storage_init/src/lib.rs +++ b/core/node/node_storage_init/src/lib.rs @@ -118,7 +118,11 @@ impl NodeStorageInitializer { recovery.initialize_storage(stop_receiver.clone()).await?; } else { anyhow::bail!( - "Snapshot recovery should be performed, but the strategy is not provided" + "Snapshot recovery should be performed, but the strategy is not provided. \ + In most of the cases this error means that the node was first started \ + with snapshots recovery enabled, but then it was disabled. \ + To get rid of this error and have the node sync from genesis \ + please clear the Node's database" ); } } diff --git a/docs/src/guides/external-node/01_intro.md b/docs/src/guides/external-node/01_intro.md index b5842e160b6c..2218ea3f153b 100644 --- a/docs/src/guides/external-node/01_intro.md +++ b/docs/src/guides/external-node/01_intro.md @@ -129,6 +129,11 @@ calls and transactions. This namespace is disabled by default and can be configured via setting `EN_API_NAMESPACES` as described in the [example config](prepared_configs/mainnet-config.env). +> [!NOTE] +> +> The traces will only start being generated for blocks synced after the debug namespace is enabled, they will not be +> backfilled! The only way to get traces for historical blocks is to fully re-sync the node + Available methods: | Method | Notes | From 75a7c08868e5f794be0c50b012164fcba5846f08 Mon Sep 17 00:00:00 2001 From: Dima Zhornyk <55756184+dimazhornyk@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:02:54 +0100 Subject: [PATCH 46/52] feat: new da_dispatcher metrics (#3464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Add new metrics to the da_dispatcher: - sealed -> dispatched lag - operator balance ## Why ❔ To improve transparency and be able to create alerts based on these values ## 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/da_client/src/lib.rs | 2 + ...62f3339768a85aaff9a509901e9f42b09097b.json | 28 - ...1fb0ee6299fddc9f89cf70c5b69c7182f0a54.json | 34 + core/lib/dal/src/data_availability_dal.rs | 5 +- .../src/models/storage_data_availability.rs | 3 +- core/node/da_clients/src/avail/client.rs | 23 + core/node/da_clients/src/avail/sdk.rs | 19 +- core/node/da_clients/src/celestia/client.rs | 7 + .../celestia/generated/cosmos.bank.v1beta1.rs | 1121 +++++++++++++++++ .../generated/cosmos.base.query.v1beta1.rs | 77 ++ core/node/da_clients/src/celestia/mod.rs | 10 + core/node/da_clients/src/celestia/sdk.rs | 32 + core/node/da_clients/src/eigen/client.rs | 4 + core/node/da_clients/src/no_da.rs | 4 + core/node/da_clients/src/object_store.rs | 4 + core/node/da_dispatcher/src/da_dispatcher.rs | 33 +- core/node/da_dispatcher/src/metrics.rs | 11 +- 17 files changed, 1380 insertions(+), 37 deletions(-) delete mode 100644 core/lib/dal/.sqlx/query-2a2083fd04ebd006eb0aa4e0e5f62f3339768a85aaff9a509901e9f42b09097b.json create mode 100644 core/lib/dal/.sqlx/query-f2eeb448a856b9e57bcc2a724791fb0ee6299fddc9f89cf70c5b69c7182f0a54.json create mode 100644 core/node/da_clients/src/celestia/generated/cosmos.bank.v1beta1.rs create mode 100644 core/node/da_clients/src/celestia/generated/cosmos.base.query.v1beta1.rs diff --git a/core/lib/da_client/src/lib.rs b/core/lib/da_client/src/lib.rs index 7e4a2643a259..dce9c0fa8d1b 100644 --- a/core/lib/da_client/src/lib.rs +++ b/core/lib/da_client/src/lib.rs @@ -23,6 +23,8 @@ pub trait DataAvailabilityClient: Sync + Send + fmt::Debug { /// Returns the maximum size of the blob (in bytes) that can be dispatched. None means no limit. fn blob_size_limit(&self) -> Option; + + async fn balance(&self) -> Result; } impl Clone for Box { diff --git a/core/lib/dal/.sqlx/query-2a2083fd04ebd006eb0aa4e0e5f62f3339768a85aaff9a509901e9f42b09097b.json b/core/lib/dal/.sqlx/query-2a2083fd04ebd006eb0aa4e0e5f62f3339768a85aaff9a509901e9f42b09097b.json deleted file mode 100644 index a713616d582c..000000000000 --- a/core/lib/dal/.sqlx/query-2a2083fd04ebd006eb0aa4e0e5f62f3339768a85aaff9a509901e9f42b09097b.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n number,\n pubdata_input\n FROM\n l1_batches\n LEFT JOIN\n data_availability\n ON data_availability.l1_batch_number = l1_batches.number\n WHERE\n eth_commit_tx_id IS NULL\n AND number != 0\n AND data_availability.blob_id IS NULL\n AND pubdata_input IS NOT NULL\n ORDER BY\n number\n LIMIT\n $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "number", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "pubdata_input", - "type_info": "Bytea" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - true - ] - }, - "hash": "2a2083fd04ebd006eb0aa4e0e5f62f3339768a85aaff9a509901e9f42b09097b" -} diff --git a/core/lib/dal/.sqlx/query-f2eeb448a856b9e57bcc2a724791fb0ee6299fddc9f89cf70c5b69c7182f0a54.json b/core/lib/dal/.sqlx/query-f2eeb448a856b9e57bcc2a724791fb0ee6299fddc9f89cf70c5b69c7182f0a54.json new file mode 100644 index 000000000000..bba0056a5ffe --- /dev/null +++ b/core/lib/dal/.sqlx/query-f2eeb448a856b9e57bcc2a724791fb0ee6299fddc9f89cf70c5b69c7182f0a54.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n number,\n pubdata_input,\n sealed_at\n FROM\n l1_batches\n LEFT JOIN\n data_availability\n ON data_availability.l1_batch_number = l1_batches.number\n WHERE\n eth_commit_tx_id IS NULL\n AND number != 0\n AND data_availability.blob_id IS NULL\n AND pubdata_input IS NOT NULL\n AND sealed_at IS NOT NULL\n ORDER BY\n number\n LIMIT\n $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "number", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "pubdata_input", + "type_info": "Bytea" + }, + { + "ordinal": 2, + "name": "sealed_at", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + true, + true + ] + }, + "hash": "f2eeb448a856b9e57bcc2a724791fb0ee6299fddc9f89cf70c5b69c7182f0a54" +} diff --git a/core/lib/dal/src/data_availability_dal.rs b/core/lib/dal/src/data_availability_dal.rs index 41dd7efe2732..8503cc21f283 100644 --- a/core/lib/dal/src/data_availability_dal.rs +++ b/core/lib/dal/src/data_availability_dal.rs @@ -184,7 +184,8 @@ impl DataAvailabilityDal<'_, '_> { r#" SELECT number, - pubdata_input + pubdata_input, + sealed_at FROM l1_batches LEFT JOIN @@ -195,6 +196,7 @@ impl DataAvailabilityDal<'_, '_> { AND number != 0 AND data_availability.blob_id IS NULL AND pubdata_input IS NOT NULL + AND sealed_at IS NOT NULL ORDER BY number LIMIT @@ -213,6 +215,7 @@ impl DataAvailabilityDal<'_, '_> { // `unwrap` is safe here because we have a `WHERE` clause that filters out `NULL` values pubdata: row.pubdata_input.unwrap(), l1_batch_number: L1BatchNumber(row.number as u32), + sealed_at: row.sealed_at.unwrap().and_utc(), }) .collect()) } diff --git a/core/lib/dal/src/models/storage_data_availability.rs b/core/lib/dal/src/models/storage_data_availability.rs index 2a1b39845e69..cfdbde5ac3a8 100644 --- a/core/lib/dal/src/models/storage_data_availability.rs +++ b/core/lib/dal/src/models/storage_data_availability.rs @@ -1,4 +1,4 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use zksync_types::{pubdata_da::DataAvailabilityBlob, L1BatchNumber}; /// Represents a blob in the data availability layer. @@ -26,4 +26,5 @@ impl From for DataAvailabilityBlob { pub struct L1BatchDA { pub pubdata: Vec, pub l1_batch_number: L1BatchNumber, + pub sealed_at: DateTime, } diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 411a0354d632..d4831ec81ce2 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -245,4 +245,27 @@ impl DataAvailabilityClient for AvailClient { fn blob_size_limit(&self) -> Option { Some(RawAvailClient::MAX_BLOB_SIZE) } + + async fn balance(&self) -> Result { + match self.sdk_client.as_ref() { + AvailClientMode::Default(client) => { + let AvailClientConfig::FullClient(default_config) = &self.config.config else { + unreachable!(); // validated in protobuf config + }; + + let ws_client = WsClientBuilder::default() + .build(default_config.api_node_url.clone().as_str()) + .await + .map_err(to_non_retriable_da_error)?; + + Ok(client + .balance(&ws_client) + .await + .map_err(to_non_retriable_da_error)?) + } + AvailClientMode::GasRelay(_) => { + Ok(0) // TODO: implement balance for gas relay (PE-304) + } + } + } } diff --git a/core/node/da_clients/src/avail/sdk.rs b/core/node/da_clients/src/avail/sdk.rs index 8f28e797dc9a..bf6bdcb13ff5 100644 --- a/core/node/da_clients/src/avail/sdk.rs +++ b/core/node/da_clients/src/avail/sdk.rs @@ -3,6 +3,7 @@ use std::{fmt::Debug, sync::Arc, time}; +use anyhow::Context; use backon::{ConstantBuilder, Retryable}; use bytes::Bytes; use jsonrpsee::{ @@ -22,7 +23,6 @@ use crate::utils::to_non_retriable_da_error; const PROTOCOL_VERSION: u8 = 4; -/// An implementation of the `DataAvailabilityClient` trait that interacts with the Avail network. #[derive(Debug, Clone)] pub(crate) struct RawAvailClient { app_id: u32, @@ -344,6 +344,23 @@ impl RawAvailClient { Ok(tx_id) } + + /// Returns the balance of the address controlled by the `keypair` + pub async fn balance(&self, client: &Client) -> anyhow::Result { + let address = to_addr(self.keypair.clone()); + let resp: serde_json::Value = client + .request("state_getStorage", rpc_params![address]) + .await + .context("Error calling state_getStorage RPC")?; + + let balance = resp + .as_str() + .ok_or_else(|| anyhow::anyhow!("Invalid balance"))?; + + balance + .parse() + .context("Unable to parse the account balance") + } } fn blake2(data: Vec) -> [u8; N] { diff --git a/core/node/da_clients/src/celestia/client.rs b/core/node/da_clients/src/celestia/client.rs index df0735d4e1e4..9dc91ed141f5 100644 --- a/core/node/da_clients/src/celestia/client.rs +++ b/core/node/da_clients/src/celestia/client.rs @@ -97,6 +97,13 @@ impl DataAvailabilityClient for CelestiaClient { fn blob_size_limit(&self) -> Option { Some(1973786) // almost 2MB } + + async fn balance(&self) -> Result { + self.client + .balance() + .await + .map_err(to_non_retriable_da_error) + } } impl Debug for CelestiaClient { diff --git a/core/node/da_clients/src/celestia/generated/cosmos.bank.v1beta1.rs b/core/node/da_clients/src/celestia/generated/cosmos.bank.v1beta1.rs new file mode 100644 index 000000000000..3eb8c536c915 --- /dev/null +++ b/core/node/da_clients/src/celestia/generated/cosmos.bank.v1beta1.rs @@ -0,0 +1,1121 @@ +// This file is @generated by prost-build. +/// Params defines the parameters for the bank module. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Params { + #[prost(message, repeated, tag = "1")] + pub send_enabled: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "2")] + pub default_send_enabled: bool, +} +impl ::prost::Name for Params { + const NAME: &'static str = "Params"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.Params".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.Params".into() + } +} +/// SendEnabled maps coin denom to a send_enabled status (whether a denom is +/// sendable). +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendEnabled { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + #[prost(bool, tag = "2")] + pub enabled: bool, +} +impl ::prost::Name for SendEnabled { + const NAME: &'static str = "SendEnabled"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.SendEnabled".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.SendEnabled".into() + } +} +/// Input models transaction input. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Input { + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "2")] + pub coins: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for Input { + const NAME: &'static str = "Input"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.Input".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.Input".into() + } +} +/// Output models transaction outputs. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Output { + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "2")] + pub coins: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for Output { + const NAME: &'static str = "Output"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.Output".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.Output".into() + } +} +/// Supply represents a struct that passively keeps track of the total supply +/// amounts in the network. +/// This message is deprecated now that supply is indexed by denom. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Supply { + #[prost(message, repeated, tag = "1")] + pub total: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for Supply { + const NAME: &'static str = "Supply"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.Supply".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.Supply".into() + } +} +/// DenomUnit represents a struct that describes a given +/// denomination unit of the basic token. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DenomUnit { + /// denom represents the string name of the given denom unit (e.g uatom). + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + /// exponent represents power of 10 exponent that one must + /// raise the base_denom to in order to equal the given DenomUnit's denom + /// 1 denom = 10^exponent base_denom + /// (e.g. with a base_denom of uatom, one can create a DenomUnit of 'atom' with + /// exponent = 6, thus: 1 atom = 10^6 uatom). + #[prost(uint32, tag = "2")] + pub exponent: u32, + /// aliases is a list of string aliases for the given denom + #[prost(string, repeated, tag = "3")] + pub aliases: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +impl ::prost::Name for DenomUnit { + const NAME: &'static str = "DenomUnit"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.DenomUnit".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.DenomUnit".into() + } +} +/// Metadata represents a struct that describes +/// a basic token. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Metadata { + #[prost(string, tag = "1")] + pub description: ::prost::alloc::string::String, + /// denom_units represents the list of DenomUnit's for a given coin + #[prost(message, repeated, tag = "2")] + pub denom_units: ::prost::alloc::vec::Vec, + /// base represents the base denom (should be the DenomUnit with exponent = 0). + #[prost(string, tag = "3")] + pub base: ::prost::alloc::string::String, + /// display indicates the suggested denom that should be + /// displayed in clients. + #[prost(string, tag = "4")] + pub display: ::prost::alloc::string::String, + /// name defines the name of the token (eg: Cosmos Atom) + /// + /// Since: cosmos-sdk 0.43 + #[prost(string, tag = "5")] + pub name: ::prost::alloc::string::String, + /// symbol is the token symbol usually shown on exchanges (eg: ATOM). This can + /// be the same as the display. + /// + /// Since: cosmos-sdk 0.43 + #[prost(string, tag = "6")] + pub symbol: ::prost::alloc::string::String, + /// URI to a document (on or off-chain) that contains additional information. Optional. + /// + /// Since: cosmos-sdk 0.46 + #[prost(string, tag = "7")] + pub uri: ::prost::alloc::string::String, + /// URIHash is a sha256 hash of a document pointed by URI. It's used to verify that + /// the document didn't change. Optional. + /// + /// Since: cosmos-sdk 0.46 + #[prost(string, tag = "8")] + pub uri_hash: ::prost::alloc::string::String, +} +impl ::prost::Name for Metadata { + const NAME: &'static str = "Metadata"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.Metadata".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.Metadata".into() + } +} +/// GenesisState defines the bank module's genesis state. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenesisState { + /// params defines all the paramaters of the module. + #[prost(message, optional, tag = "1")] + pub params: ::core::option::Option, + /// balances is an array containing the balances of all the accounts. + #[prost(message, repeated, tag = "2")] + pub balances: ::prost::alloc::vec::Vec, + /// supply represents the total supply. If it is left empty, then supply will be calculated based on the provided + /// balances. Otherwise, it will be used to validate that the sum of the balances equals this amount. + #[prost(message, repeated, tag = "3")] + pub supply: ::prost::alloc::vec::Vec, + /// denom_metadata defines the metadata of the differents coins. + #[prost(message, repeated, tag = "4")] + pub denom_metadata: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for GenesisState { + const NAME: &'static str = "GenesisState"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.GenesisState".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.GenesisState".into() + } +} +/// Balance defines an account address and balance pair used in the bank module's +/// genesis state. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Balance { + /// address is the address of the balance holder. + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + /// coins defines the different coins this balance holds. + #[prost(message, repeated, tag = "2")] + pub coins: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for Balance { + const NAME: &'static str = "Balance"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.Balance".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.Balance".into() + } +} +/// QueryBalanceRequest is the request type for the Query/Balance RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryBalanceRequest { + /// address is the address to query balances for. + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + /// denom is the coin denom to query balances for. + #[prost(string, tag = "2")] + pub denom: ::prost::alloc::string::String, +} +impl ::prost::Name for QueryBalanceRequest { + const NAME: &'static str = "QueryBalanceRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryBalanceRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryBalanceRequest".into() + } +} +/// QueryBalanceResponse is the response type for the Query/Balance RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryBalanceResponse { + /// balance is the balance of the coin. + #[prost(message, optional, tag = "1")] + pub balance: ::core::option::Option, +} +impl ::prost::Name for QueryBalanceResponse { + const NAME: &'static str = "QueryBalanceResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryBalanceResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryBalanceResponse".into() + } +} +/// QueryBalanceRequest is the request type for the Query/AllBalances RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryAllBalancesRequest { + /// address is the address to query balances for. + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageRequest, + >, +} +impl ::prost::Name for QueryAllBalancesRequest { + const NAME: &'static str = "QueryAllBalancesRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryAllBalancesRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryAllBalancesRequest".into() + } +} +/// QueryAllBalancesResponse is the response type for the Query/AllBalances RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryAllBalancesResponse { + /// balances is the balances of all the coins. + #[prost(message, repeated, tag = "1")] + pub balances: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageResponse, + >, +} +impl ::prost::Name for QueryAllBalancesResponse { + const NAME: &'static str = "QueryAllBalancesResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryAllBalancesResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryAllBalancesResponse".into() + } +} +/// QuerySpendableBalancesRequest defines the gRPC request structure for querying +/// an account's spendable balances. +/// +/// Since: cosmos-sdk 0.46 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QuerySpendableBalancesRequest { + /// address is the address to query spendable balances for. + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageRequest, + >, +} +impl ::prost::Name for QuerySpendableBalancesRequest { + const NAME: &'static str = "QuerySpendableBalancesRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QuerySpendableBalancesRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QuerySpendableBalancesRequest".into() + } +} +/// QuerySpendableBalancesResponse defines the gRPC response structure for querying +/// an account's spendable balances. +/// +/// Since: cosmos-sdk 0.46 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QuerySpendableBalancesResponse { + /// balances is the spendable balances of all the coins. + #[prost(message, repeated, tag = "1")] + pub balances: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageResponse, + >, +} +impl ::prost::Name for QuerySpendableBalancesResponse { + const NAME: &'static str = "QuerySpendableBalancesResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QuerySpendableBalancesResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QuerySpendableBalancesResponse".into() + } +} +/// QueryTotalSupplyRequest is the request type for the Query/TotalSupply RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryTotalSupplyRequest { + /// pagination defines an optional pagination for the request. + /// + /// Since: cosmos-sdk 0.43 + #[prost(message, optional, tag = "1")] + pub pagination: ::core::option::Option< + super::super::base::query::PageRequest, + >, +} +impl ::prost::Name for QueryTotalSupplyRequest { + const NAME: &'static str = "QueryTotalSupplyRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryTotalSupplyRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryTotalSupplyRequest".into() + } +} +/// QueryTotalSupplyResponse is the response type for the Query/TotalSupply RPC +/// method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryTotalSupplyResponse { + /// supply is the supply of the coins + #[prost(message, repeated, tag = "1")] + pub supply: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + /// + /// Since: cosmos-sdk 0.43 + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageResponse, + >, +} +impl ::prost::Name for QueryTotalSupplyResponse { + const NAME: &'static str = "QueryTotalSupplyResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryTotalSupplyResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryTotalSupplyResponse".into() + } +} +/// QuerySupplyOfRequest is the request type for the Query/SupplyOf RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QuerySupplyOfRequest { + /// denom is the coin denom to query balances for. + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, +} +impl ::prost::Name for QuerySupplyOfRequest { + const NAME: &'static str = "QuerySupplyOfRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QuerySupplyOfRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QuerySupplyOfRequest".into() + } +} +/// QuerySupplyOfResponse is the response type for the Query/SupplyOf RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QuerySupplyOfResponse { + /// amount is the supply of the coin. + #[prost(message, optional, tag = "1")] + pub amount: ::core::option::Option, +} +impl ::prost::Name for QuerySupplyOfResponse { + const NAME: &'static str = "QuerySupplyOfResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QuerySupplyOfResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QuerySupplyOfResponse".into() + } +} +/// QueryParamsRequest defines the request type for querying x/bank parameters. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct QueryParamsRequest {} +impl ::prost::Name for QueryParamsRequest { + const NAME: &'static str = "QueryParamsRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryParamsRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryParamsRequest".into() + } +} +/// QueryParamsResponse defines the response type for querying x/bank parameters. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryParamsResponse { + #[prost(message, optional, tag = "1")] + pub params: ::core::option::Option, +} +impl ::prost::Name for QueryParamsResponse { + const NAME: &'static str = "QueryParamsResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryParamsResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryParamsResponse".into() + } +} +/// QueryDenomsMetadataRequest is the request type for the Query/DenomsMetadata RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDenomsMetadataRequest { + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag = "1")] + pub pagination: ::core::option::Option< + super::super::base::query::PageRequest, + >, +} +impl ::prost::Name for QueryDenomsMetadataRequest { + const NAME: &'static str = "QueryDenomsMetadataRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryDenomsMetadataRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryDenomsMetadataRequest".into() + } +} +/// QueryDenomsMetadataResponse is the response type for the Query/DenomsMetadata RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDenomsMetadataResponse { + /// metadata provides the client information for all the registered tokens. + #[prost(message, repeated, tag = "1")] + pub metadatas: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageResponse, + >, +} +impl ::prost::Name for QueryDenomsMetadataResponse { + const NAME: &'static str = "QueryDenomsMetadataResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryDenomsMetadataResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryDenomsMetadataResponse".into() + } +} +/// QueryDenomMetadataRequest is the request type for the Query/DenomMetadata RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDenomMetadataRequest { + /// denom is the coin denom to query the metadata for. + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, +} +impl ::prost::Name for QueryDenomMetadataRequest { + const NAME: &'static str = "QueryDenomMetadataRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryDenomMetadataRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryDenomMetadataRequest".into() + } +} +/// QueryDenomMetadataResponse is the response type for the Query/DenomMetadata RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDenomMetadataResponse { + /// metadata describes and provides all the client information for the requested token. + #[prost(message, optional, tag = "1")] + pub metadata: ::core::option::Option, +} +impl ::prost::Name for QueryDenomMetadataResponse { + const NAME: &'static str = "QueryDenomMetadataResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryDenomMetadataResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryDenomMetadataResponse".into() + } +} +/// QueryDenomOwnersRequest defines the request type for the DenomOwners RPC query, +/// which queries for a paginated set of all account holders of a particular +/// denomination. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDenomOwnersRequest { + /// denom defines the coin denomination to query all account holders for. + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageRequest, + >, +} +impl ::prost::Name for QueryDenomOwnersRequest { + const NAME: &'static str = "QueryDenomOwnersRequest"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryDenomOwnersRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryDenomOwnersRequest".into() + } +} +/// DenomOwner defines structure representing an account that owns or holds a +/// particular denominated token. It contains the account address and account +/// balance of the denominated token. +/// +/// Since: cosmos-sdk 0.46 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DenomOwner { + /// address defines the address that owns a particular denomination. + #[prost(string, tag = "1")] + pub address: ::prost::alloc::string::String, + /// balance is the balance of the denominated coin for an account. + #[prost(message, optional, tag = "2")] + pub balance: ::core::option::Option, +} +impl ::prost::Name for DenomOwner { + const NAME: &'static str = "DenomOwner"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.DenomOwner".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.DenomOwner".into() + } +} +/// QueryDenomOwnersResponse defines the RPC response of a DenomOwners RPC query. +/// +/// Since: cosmos-sdk 0.46 +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDenomOwnersResponse { + #[prost(message, repeated, tag = "1")] + pub denom_owners: ::prost::alloc::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag = "2")] + pub pagination: ::core::option::Option< + super::super::base::query::PageResponse, + >, +} +impl ::prost::Name for QueryDenomOwnersResponse { + const NAME: &'static str = "QueryDenomOwnersResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.QueryDenomOwnersResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.QueryDenomOwnersResponse".into() + } +} +/// Generated client implementations. +pub mod query_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + /// Query defines the gRPC querier service. + #[derive(Debug, Clone)] + pub struct QueryClient { + inner: tonic::client::Grpc, + } + impl QueryClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> QueryClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + QueryClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + /// Balance queries the balance of a single coin for a single account. + pub async fn balance( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/Balance", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "Balance")); + self.inner.unary(req, path, codec).await + } + /// AllBalances queries the balance of all coins for a single account. + pub async fn all_balances( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/AllBalances", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "AllBalances")); + self.inner.unary(req, path, codec).await + } + /// SpendableBalances queries the spenable balance of all coins for a single + /// account. + /// + /// Since: cosmos-sdk 0.46 + pub async fn spendable_balances( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/SpendableBalances", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("cosmos.bank.v1beta1.Query", "SpendableBalances"), + ); + self.inner.unary(req, path, codec).await + } + /// TotalSupply queries the total supply of all coins. + pub async fn total_supply( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/TotalSupply", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "TotalSupply")); + self.inner.unary(req, path, codec).await + } + /// SupplyOf queries the supply of a single coin. + pub async fn supply_of( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/SupplyOf", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "SupplyOf")); + self.inner.unary(req, path, codec).await + } + /// Params queries the parameters of x/bank module. + pub async fn params( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/Params", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "Params")); + self.inner.unary(req, path, codec).await + } + /// DenomsMetadata queries the client metadata of a given coin denomination. + pub async fn denom_metadata( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/DenomMetadata", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "DenomMetadata")); + self.inner.unary(req, path, codec).await + } + /// DenomsMetadata queries the client metadata for all registered coin + /// denominations. + pub async fn denoms_metadata( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/DenomsMetadata", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "DenomsMetadata")); + self.inner.unary(req, path, codec).await + } + /// DenomOwners queries for all account addresses that own a particular token + /// denomination. + /// + /// Since: cosmos-sdk 0.46 + pub async fn denom_owners( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Query/DenomOwners", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Query", "DenomOwners")); + self.inner.unary(req, path, codec).await + } + } +} +/// MsgSend represents a message to send coins from one account to another. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgSend { + #[prost(string, tag = "1")] + pub from_address: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub to_address: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "3")] + pub amount: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for MsgSend { + const NAME: &'static str = "MsgSend"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.MsgSend".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.MsgSend".into() + } +} +/// MsgSendResponse defines the Msg/Send response type. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct MsgSendResponse {} +impl ::prost::Name for MsgSendResponse { + const NAME: &'static str = "MsgSendResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.MsgSendResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.MsgSendResponse".into() + } +} +/// MsgMultiSend represents an arbitrary multi-in, multi-out send message. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgMultiSend { + #[prost(message, repeated, tag = "1")] + pub inputs: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "2")] + pub outputs: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for MsgMultiSend { + const NAME: &'static str = "MsgMultiSend"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.MsgMultiSend".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.MsgMultiSend".into() + } +} +/// MsgMultiSendResponse defines the Msg/MultiSend response type. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct MsgMultiSendResponse {} +impl ::prost::Name for MsgMultiSendResponse { + const NAME: &'static str = "MsgMultiSendResponse"; + const PACKAGE: &'static str = "cosmos.bank.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.bank.v1beta1.MsgMultiSendResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.bank.v1beta1.MsgMultiSendResponse".into() + } +} +/// Generated client implementations. +pub mod msg_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + /// Msg defines the bank Msg service. + #[derive(Debug, Clone)] + pub struct MsgClient { + inner: tonic::client::Grpc, + } + impl MsgClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> MsgClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + MsgClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + /// Send defines a method for sending coins from one account to another account. + pub async fn send( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Msg/Send", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Msg", "Send")); + self.inner.unary(req, path, codec).await + } + /// MultiSend defines a method for sending coins from some accounts to other accounts. + pub async fn multi_send( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cosmos.bank.v1beta1.Msg/MultiSend", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.bank.v1beta1.Msg", "MultiSend")); + self.inner.unary(req, path, codec).await + } + } +} diff --git a/core/node/da_clients/src/celestia/generated/cosmos.base.query.v1beta1.rs b/core/node/da_clients/src/celestia/generated/cosmos.base.query.v1beta1.rs new file mode 100644 index 000000000000..b236f3026d3f --- /dev/null +++ b/core/node/da_clients/src/celestia/generated/cosmos.base.query.v1beta1.rs @@ -0,0 +1,77 @@ +// This file is @generated by prost-build. +/// PageRequest is to be embedded in gRPC request messages for efficient +/// pagination. Ex: +/// +/// message SomeRequest { +/// Foo some_parameter = 1; +/// PageRequest pagination = 2; +/// } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PageRequest { + /// key is a value returned in PageResponse.next_key to begin + /// querying the next page most efficiently. Only one of offset or key + /// should be set. + #[prost(bytes = "vec", tag = "1")] + pub key: ::prost::alloc::vec::Vec, + /// offset is a numeric offset that can be used when key is unavailable. + /// It is less efficient than using key. Only one of offset or key should + /// be set. + #[prost(uint64, tag = "2")] + pub offset: u64, + /// limit is the total number of results to be returned in the result page. + /// If left empty it will default to a value to be set by each app. + #[prost(uint64, tag = "3")] + pub limit: u64, + /// count_total is set to true to indicate that the result set should include + /// a count of the total number of items available for pagination in UIs. + /// count_total is only respected when offset is used. It is ignored when key + /// is set. + #[prost(bool, tag = "4")] + pub count_total: bool, + /// reverse is set to true if results are to be returned in the descending order. + /// + /// Since: cosmos-sdk 0.43 + #[prost(bool, tag = "5")] + pub reverse: bool, +} +impl ::prost::Name for PageRequest { + const NAME: &'static str = "PageRequest"; + const PACKAGE: &'static str = "cosmos.base.query.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.base.query.v1beta1.PageRequest".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.base.query.v1beta1.PageRequest".into() + } +} +/// PageResponse is to be embedded in gRPC response messages where the +/// corresponding request message has used PageRequest. +/// +/// message SomeResponse { +/// repeated Bar results = 1; +/// PageResponse page = 2; +/// } +#[derive(::serde::Deserialize, ::serde::Serialize)] +#[serde(default)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PageResponse { + /// next_key is the key to be passed to PageRequest.key to + /// query the next page most efficiently. It will be empty if + /// there are no more results. + #[prost(bytes = "vec", tag = "1")] + pub next_key: ::prost::alloc::vec::Vec, + /// total is total number of results available if PageRequest.count_total + /// was set, its value is undefined otherwise + #[prost(uint64, tag = "2")] + pub total: u64, +} +impl ::prost::Name for PageResponse { + const NAME: &'static str = "PageResponse"; + const PACKAGE: &'static str = "cosmos.base.query.v1beta1"; + fn full_name() -> ::prost::alloc::string::String { + "cosmos.base.query.v1beta1.PageResponse".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/cosmos.base.query.v1beta1.PageResponse".into() + } +} diff --git a/core/node/da_clients/src/celestia/mod.rs b/core/node/da_clients/src/celestia/mod.rs index ce648531f282..1baa52466cf2 100644 --- a/core/node/da_clients/src/celestia/mod.rs +++ b/core/node/da_clients/src/celestia/mod.rs @@ -24,6 +24,16 @@ pub mod cosmos { pub mod v1beta1 { include!("generated/cosmos.base.v1beta1.rs"); } + + pub mod query { + include!("generated/cosmos.base.query.v1beta1.rs"); + } + } + + pub mod bank { + pub mod v1beta1 { + include!("generated/cosmos.bank.v1beta1.rs"); + } } pub mod tx { diff --git a/core/node/da_clients/src/celestia/sdk.rs b/core/node/da_clients/src/celestia/sdk.rs index 5fd9aea79f07..11f10d823f31 100644 --- a/core/node/da_clients/src/celestia/sdk.rs +++ b/core/node/da_clients/src/celestia/sdk.rs @@ -20,6 +20,7 @@ use super::{ query_client::QueryClient as AuthQueryClient, BaseAccount, QueryAccountRequest, QueryParamsRequest as QueryAuthParamsRequest, }, + bank::v1beta1::{query_client::QueryClient as BankQueryClient, QueryAllBalancesRequest}, base::{ node::{ service_client::ServiceClient as MinGasPriceClient, @@ -377,6 +378,37 @@ impl RawCelestiaClient { tracing::debug!(tx_hash = %tx_response.txhash, height, "transaction succeeded"); Ok(Some(height)) } + + pub async fn balance(&self) -> anyhow::Result { + let mut auth_query_client = BankQueryClient::new(self.grpc_channel.clone()); + let resp = auth_query_client + .all_balances(QueryAllBalancesRequest { + address: self.address.clone(), + pagination: None, + }) + .await?; + + let micro_tia_balance = resp + .into_inner() + .balances + .into_iter() + .find(|coin| coin.denom == UNITS_SUFFIX) + .map_or_else( + || { + Err(anyhow::anyhow!( + "no balance found for address: {}", + self.address + )) + }, + |coin| { + coin.amount + .parse::() + .map_err(|e| anyhow::anyhow!("failed to parse balance: {}", e)) + }, + )?; + + Ok(micro_tia_balance) + } } /// Returns a `BlobTx` for the given signed tx and blobs. diff --git a/core/node/da_clients/src/eigen/client.rs b/core/node/da_clients/src/eigen/client.rs index d977620526aa..c7404344e7df 100644 --- a/core/node/da_clients/src/eigen/client.rs +++ b/core/node/da_clients/src/eigen/client.rs @@ -62,4 +62,8 @@ impl DataAvailabilityClient for EigenClient { fn blob_size_limit(&self) -> Option { Some(1920 * 1024) // 2mb - 128kb as a buffer } + + async fn balance(&self) -> Result { + Ok(0) // TODO fetch from API when payments are enabled in Eigen (PE-305) + } } diff --git a/core/node/da_clients/src/no_da.rs b/core/node/da_clients/src/no_da.rs index db0557510ed2..ecfa78ba44de 100644 --- a/core/node/da_clients/src/no_da.rs +++ b/core/node/da_clients/src/no_da.rs @@ -25,4 +25,8 @@ impl DataAvailabilityClient for NoDAClient { fn blob_size_limit(&self) -> Option { None } + + async fn balance(&self) -> Result { + Ok(0) + } } diff --git a/core/node/da_clients/src/object_store.rs b/core/node/da_clients/src/object_store.rs index 55764e8260e0..8c652e1e2341 100644 --- a/core/node/da_clients/src/object_store.rs +++ b/core/node/da_clients/src/object_store.rs @@ -87,6 +87,10 @@ impl DataAvailabilityClient for ObjectStoreDAClient { fn blob_size_limit(&self) -> Option { None } + + async fn balance(&self) -> Result { + Ok(0) + } } /// Used as a wrapper for the pubdata to be stored in the GCS. diff --git a/core/node/da_dispatcher/src/da_dispatcher.rs b/core/node/da_dispatcher/src/da_dispatcher.rs index f59a30b362ee..3bdb5406c92a 100644 --- a/core/node/da_dispatcher/src/da_dispatcher.rs +++ b/core/node/da_dispatcher/src/da_dispatcher.rs @@ -103,7 +103,7 @@ impl DataAvailabilityDispatcher { .await?; drop(conn); - for batch in batches { + for batch in &batches { let dispatch_latency = METRICS.blob_dispatch_latency.start(); let dispatch_response = retry(self.config.max_retries(), batch.l1_batch_number, || { self.client @@ -119,14 +119,14 @@ impl DataAvailabilityDispatcher { })?; let dispatch_latency_duration = dispatch_latency.observe(); - let sent_at = Utc::now().naive_utc(); + let sent_at = Utc::now(); let mut conn = self.pool.connection_tagged("da_dispatcher").await?; conn.data_availability_dal() .insert_l1_batch_da( batch.l1_batch_number, dispatch_response.blob_id.as_str(), - sent_at, + sent_at.naive_utc(), ) .await?; drop(conn); @@ -135,6 +135,12 @@ impl DataAvailabilityDispatcher { .last_dispatched_l1_batch .set(batch.l1_batch_number.0 as usize); METRICS.blob_size.observe(batch.pubdata.len()); + METRICS.sealed_to_dispatched_lag.observe( + sent_at + .signed_duration_since(batch.sealed_at) + .to_std() + .context("sent_at has to be higher than sealed_at")?, + ); tracing::info!( "Dispatched a DA for batch_number: {}, pubdata_size: {}, dispatch_latency: {dispatch_latency_duration:?}", batch.l1_batch_number, @@ -142,6 +148,27 @@ impl DataAvailabilityDispatcher { ); } + // We don't need to report this metric every iteration, only once when the balance is changed + if !batches.is_empty() { + let client_arc = Arc::new(self.client.clone_boxed()); + + tokio::spawn(async move { + let balance = client_arc + .balance() + .await + .with_context(|| "Unable to retrieve DA operator balance"); + + match balance { + Ok(balance) => { + METRICS.operator_balance.set(balance); + } + Err(err) => { + tracing::error!("{err}") + } + } + }); + } + Ok(()) } diff --git a/core/node/da_dispatcher/src/metrics.rs b/core/node/da_dispatcher/src/metrics.rs index 67ac5ed68222..2e167f2083b3 100644 --- a/core/node/da_dispatcher/src/metrics.rs +++ b/core/node/da_dispatcher/src/metrics.rs @@ -4,12 +4,12 @@ use vise::{Buckets, Gauge, Histogram, Metrics, Unit}; /// Buckets for `blob_dispatch_latency` (from 0.1 to 120 seconds). const DISPATCH_LATENCIES: Buckets = - Buckets::values(&[0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0, 120.0]); + Buckets::values(&[0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0, 120.0, 240.0]); #[derive(Debug, Metrics)] #[metrics(prefix = "server_da_dispatcher")] pub(super) struct DataAvailabilityDispatcherMetrics { - /// Latency of the dispatch of the blob. + /// Latency of the dispatch of the blob. Only the communication with DA layer. #[metrics(buckets = DISPATCH_LATENCIES, unit = Unit::Seconds)] pub blob_dispatch_latency: Histogram, /// The duration between the moment when the blob is dispatched and the moment when it is included. @@ -19,7 +19,6 @@ pub(super) struct DataAvailabilityDispatcherMetrics { /// Buckets are bytes ranging from 1 KB to 16 MB, which has to satisfy all blob size values. #[metrics(buckets = Buckets::exponential(1_024.0..=16.0 * 1_024.0 * 1_024.0, 2.0), unit = Unit::Bytes)] pub blob_size: Histogram, - /// Number of transactions resent by the DA dispatcher. #[metrics(buckets = Buckets::linear(0.0..=10.0, 1.0))] pub dispatch_call_retries: Histogram, @@ -27,6 +26,12 @@ pub(super) struct DataAvailabilityDispatcherMetrics { pub last_dispatched_l1_batch: Gauge, /// Last L1 batch that has its inclusion finalized by DA layer. pub last_included_l1_batch: Gauge, + /// The delay between the moment batch was sealed and the moment it was dispatched. Includes + /// both communication with DA layer and time it spends in the queue on the `da_dispatcher` side. + #[metrics(buckets = DISPATCH_LATENCIES, unit = Unit::Seconds)] + pub sealed_to_dispatched_lag: Histogram, + /// The balance of the operator wallet on DA network. + pub operator_balance: Gauge, } #[vise::register] From e3759a27158b2d6202fca19ada5344ddff36685b Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 28 Jan 2025 18:41:36 +0200 Subject: [PATCH 47/52] fix(zk_toolbox): Fix path to prover data handler port (#3542) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Fixes a path to the prover data handler port. ## Why ❔ Without this fix, the prover gateway config is set up incorrectly. ## 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] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs b/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs index 4c74e194c4ee..dafc786333da 100644 --- a/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs +++ b/zkstack_cli/crates/zkstack/src/commands/chain/init/configs.rs @@ -63,7 +63,7 @@ pub async fn init_configs( let mut general_config = chain_config.get_general_config().await?.patched(); let prover_data_handler_port = general_config .base() - .get_opt::("proof_data_handler.http_port")?; + .get_opt::("data_handler.http_port")?; if let Some(port) = prover_data_handler_port { general_config.insert("prover_gateway.api_url", format!("http://127.0.0.1:{port}"))?; } From e7eb716c241c8bf224361fee150f9d1fe3023ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Grze=C5=9Bkiewicz?= Date: Wed, 29 Jan 2025 15:52:49 +0100 Subject: [PATCH 48/52] feat(en): better EN default req entities limit, improved documentation for API limits (#3546) --- core/bin/external_node/src/config/mod.rs | 2 +- docs/src/guides/external-node/02_configuration.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/bin/external_node/src/config/mod.rs b/core/bin/external_node/src/config/mod.rs index 3f1727c619b4..9274edf9d1ab 100644 --- a/core/bin/external_node/src/config/mod.rs +++ b/core/bin/external_node/src/config/mod.rs @@ -760,7 +760,7 @@ impl OptionalENConfig { } const fn default_req_entities_limit() -> usize { - 1_024 + 10_000 } const fn default_max_tx_size_bytes() -> usize { diff --git a/docs/src/guides/external-node/02_configuration.md b/docs/src/guides/external-node/02_configuration.md index 90da7c1eea79..e570b1e9a1a3 100644 --- a/docs/src/guides/external-node/02_configuration.md +++ b/docs/src/guides/external-node/02_configuration.md @@ -52,6 +52,15 @@ There are variables that allow you to fine-tune the limits of the RPC servers, s entries or the limit for the accepted transaction size. Provided files contain sane defaults that are recommended for use, but these can be edited, e.g. to make the Node more/less restrictive. +**Some common api limits config:**\ +`EN_MAX_RESPONSE_BODY_SIZE_MB` (default 10 i.e. 10MB) controls max size of a single response. Hitting the limit will +result in errors similar to:\ +`Response is too big (...)` + +`EN_REQ_ENTITIES_LIMIT` (default 10000) controls max possible limit of entities to be requested at once. Hitting the +limit will result in errors similar to:\ +`Query returned more than 10000 results (...)` + ## JSON-RPC API namespaces There are 7 total supported API namespaces: `eth`, `net`, `web3`, `debug` - standard ones; `zks` - rollup-specific one; From a075b229f054aa21415ef8462b8ad07e4985e678 Mon Sep 17 00:00:00 2001 From: EmilLuta Date: Wed, 29 Jan 2025 20:15:26 +0100 Subject: [PATCH 49/52] chore: Add logging to witness generator db save (#3551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A lot of prover infra relies on tracing. Tracing is not available on all environments. As such, troubleshooting issues covered by tracing is not possible. A recent service degradation could not be troubleshot due to lack of tracing. This line enables troubleshooting even when tracing is not available. ## 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`. --- prover/crates/bin/witness_generator/src/rounds/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prover/crates/bin/witness_generator/src/rounds/mod.rs b/prover/crates/bin/witness_generator/src/rounds/mod.rs index 6fd72c968693..6da6f5bb393d 100644 --- a/prover/crates/bin/witness_generator/src/rounds/mod.rs +++ b/prover/crates/bin/witness_generator/src/rounds/mod.rs @@ -173,6 +173,9 @@ where artifacts, ) .await?; + + tracing::info!("Saved {:?} to database for job {:?}", R::ROUND, job_id); + Ok(()) } From bf9fe85f4fd1d739105e7b21d0eebb377f752bac Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Thu, 30 Jan 2025 17:43:07 +0400 Subject: [PATCH 50/52] feat(contract-verifier): Partial matching & automatic verification (#3527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #3190 Fixes #3309 I still have to test the migration logic (want to make sure that Era mainnet data can be migrated correctly & quickly), but the PR is reviewable otherwise. This one is much bigger than I indended it to be, sorry 🥲 - Changes `zksolc` used in contract verifier tests to 1.5.10 (old one didn't have `ipfs` metadata hash support). - Fixes problems that occured with newer compilers (e.g. `Yul` bytecode extraction). - Makes it easier to run contract verifier tests locally (pins compiler versions, so that if you have a lot of compilers locally, tests don't crash). - Introduces partial matching for contracts - Reworks the schema so that contracts with matching bytecode can be "automatically" verified. - Adds a migration to the new table. --- .github/workflows/ci-core-reusable.yml | 4 +- core/Cargo.lock | 2 + core/Cargo.toml | 5 +- core/bin/contract-verifier/src/main.rs | 30 +- core/bin/verified_sources_fetcher/src/main.rs | 2 +- core/lib/basic_types/src/bytecode.rs | 8 + .../contract_verifier/src/compilers/mod.rs | 2 +- .../contract_verifier/src/compilers/solc.rs | 2 +- .../contract_verifier/src/compilers/vyper.rs | 2 +- .../contract_verifier/src/compilers/zksolc.rs | 26 +- .../src/compilers/zkvyper.rs | 2 +- core/lib/contract_verifier/src/lib.rs | 64 ++- .../lib/contract_verifier/src/resolver/mod.rs | 2 +- core/lib/contract_verifier/src/tests/mod.rs | 22 +- core/lib/contract_verifier/src/tests/real.rs | 305 ++++++++++++-- ...cb2685cda1ae7ecca83062ede7320c3b4a427.json | 15 - ...bffc78d92adb3c1e3ca60b12163e38c67047e.json | 22 - ...3ed5f1e6eac5b71232c784abd4d4cd8677805.json | 17 + ...046b7820e39b63ed4f98bcaa3b3f305cbe576.json | 26 ++ ...072dcee6e6f8439e6b43eebd6df5563a4d0b9.json | 35 ++ ...989ff52100e6a93537c35961e62268d7cd26e.json | 29 ++ ...80a651e28abb3dd3582211ceb6b2bb8009258.json | 20 + ...48743ffed494316750be3b0ffb10b2fc09e93.json | 22 + core/lib/dal/Cargo.toml | 1 + ...2800_contract-verifier-new-schema.down.sql | 3 + ...102800_contract-verifier-new-schema.up.sql | 13 + core/lib/dal/src/contract_verification_dal.rs | 310 ++++++++++++-- .../models/storage_verification_request.rs | 2 +- core/lib/types/Cargo.toml | 1 + .../api.rs} | 26 ++ .../contract_identifier.rs | 396 ++++++++++++++++++ .../types/src/contract_verification/mod.rs | 2 + core/lib/types/src/lib.rs | 2 +- .../src/api_impl.rs | 82 +++- .../contract_verification_server/src/cache.rs | 2 +- .../contract_verification_server/src/tests.rs | 2 +- core/tests/ts-integration/hardhat.config.ts | 2 +- .../ts-integration/scripts/compile-yul.ts | 2 +- .../tests/api/contract-verification.test.ts | 2 +- .../ts-integration/tests/api/debug.test.ts | 2 +- prover/Cargo.lock | 39 ++ zkstack_cli/Cargo.lock | 38 ++ 42 files changed, 1442 insertions(+), 149 deletions(-) delete mode 100644 core/lib/dal/.sqlx/query-1823e1ac602ce4ba1db06543af9cb2685cda1ae7ecca83062ede7320c3b4a427.json delete mode 100644 core/lib/dal/.sqlx/query-2d0c2e9ec4187641baef8a33229bffc78d92adb3c1e3ca60b12163e38c67047e.json create mode 100644 core/lib/dal/.sqlx/query-349d41c8ce192e82152e9d254c23ed5f1e6eac5b71232c784abd4d4cd8677805.json create mode 100644 core/lib/dal/.sqlx/query-668cf72b78c6071340143ba9498046b7820e39b63ed4f98bcaa3b3f305cbe576.json create mode 100644 core/lib/dal/.sqlx/query-6cb50a8fbe1341ba7ea496bb0f2072dcee6e6f8439e6b43eebd6df5563a4d0b9.json create mode 100644 core/lib/dal/.sqlx/query-a331b209eafd82595ad75e24135989ff52100e6a93537c35961e62268d7cd26e.json create mode 100644 core/lib/dal/.sqlx/query-ca2c63a0e25406eec4e92a7cfda80a651e28abb3dd3582211ceb6b2bb8009258.json create mode 100644 core/lib/dal/.sqlx/query-daa2ad4ebde17808e059aa6bcf148743ffed494316750be3b0ffb10b2fc09e93.json create mode 100644 core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.down.sql create mode 100644 core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.up.sql rename core/lib/types/src/{contract_verification_api.rs => contract_verification/api.rs} (92%) create mode 100644 core/lib/types/src/contract_verification/contract_identifier.rs create mode 100644 core/lib/types/src/contract_verification/mod.rs diff --git a/.github/workflows/ci-core-reusable.yml b/.github/workflows/ci-core-reusable.yml index 1127204c552f..3a399cc8f738 100644 --- a/.github/workflows/ci-core-reusable.yml +++ b/.github/workflows/ci-core-reusable.yml @@ -72,7 +72,7 @@ jobs: ci_run zkstack dev contracts - name: Download compilers for contract verifier tests - run: ci_run zkstack contract-verifier init --zksolc-version=v1.5.3 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era + run: ci_run zkstack contract-verifier init --zksolc-version=v1.5.10 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era - name: Rust unit tests run: | @@ -431,7 +431,7 @@ jobs: - name: Initialize Contract verifier run: | - ci_run zkstack contract-verifier init --zksolc-version=v1.5.3 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era + ci_run zkstack contract-verifier init --zksolc-version=v1.5.10 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only --chain era ci_run zkstack contract-verifier run --chain era &> ${{ env.SERVER_LOGS_DIR }}/contract-verifier-rollup.log & ci_run zkstack contract-verifier wait --chain era --verbose diff --git a/core/Cargo.lock b/core/Cargo.lock index f90bc00bb68d..e8943b2a369a 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -11848,6 +11848,7 @@ dependencies = [ "itertools 0.10.5", "prost 0.12.6", "rand 0.8.5", + "rayon", "serde", "serde_json", "sqlx", @@ -13017,6 +13018,7 @@ dependencies = [ "bincode", "blake2 0.10.6", "chrono", + "ciborium", "derive_more 1.0.0", "hex", "itertools 0.10.5", diff --git a/core/Cargo.toml b/core/Cargo.toml index fca865414829..e636e1079f13 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -170,6 +170,7 @@ serde = "1" serde_json = "1" serde_with = "1" serde_yaml = "0.9" +ciborium = "0.2" sha2 = "0.10.8" sha3 = "0.10.8" sqlx = "0.8.1" @@ -231,7 +232,7 @@ tokio-stream = "0.1.16" circuit_encodings = "=0.150.20" circuit_sequencer_api = "=0.150.20" circuit_definitions = "=0.150.20" -crypto_codegen = { package = "zksync_solidity_vk_codegen",version = "=0.30.13" } +crypto_codegen = { package = "zksync_solidity_vk_codegen", version = "=0.30.13" } kzg = { package = "zksync_kzg", version = "=0.150.20" } zk_evm = { version = "=0.133.0" } zk_evm_1_3_1 = { package = "zk_evm", version = "0.131.0-rc.2" } @@ -240,7 +241,7 @@ zk_evm_1_4_0 = { package = "zk_evm", version = "0.140" } zk_evm_1_4_1 = { package = "zk_evm", version = "0.141" } zk_evm_1_5_0 = { package = "zk_evm", version = "=0.150.20" } fflonk = "=0.30.13" -bellman = {package = "zksync_bellman", version = "=0.30.13"} +bellman = { package = "zksync_bellman", version = "=0.30.13" } # New VM; pinned to a specific commit because of instability zksync_vm2 = { git = "https://github.com/matter-labs/vm2.git", rev = "457d8a7eea9093af9440662e33e598c13ba41633" } diff --git a/core/bin/contract-verifier/src/main.rs b/core/bin/contract-verifier/src/main.rs index ab86c147977d..93f23816c67d 100644 --- a/core/bin/contract-verifier/src/main.rs +++ b/core/bin/contract-verifier/src/main.rs @@ -6,7 +6,7 @@ use tokio::sync::watch; use zksync_config::configs::PrometheusConfig; use zksync_contract_verifier_lib::ContractVerifier; use zksync_core_leftovers::temp_config_store::{load_database_secrets, load_general_config}; -use zksync_dal::{ConnectionPool, Core}; +use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_queued_job_processor::JobProcessor; use zksync_utils::wait_for_tasks::ManagedTasks; use zksync_vlog::prometheus::PrometheusExporterConfig; @@ -25,6 +25,32 @@ struct Opt { secrets_path: Option, } +async fn perform_storage_migration(pool: &ConnectionPool) -> anyhow::Result<()> { + const BATCH_SIZE: usize = 1000; + + // Make it possible to override just in case. + let batch_size = std::env::var("CONTRACT_VERIFIER_MIGRATION_BATCH_SIZE") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(BATCH_SIZE); + + let mut storage = pool.connection().await?; + let migration_performed = storage + .contract_verification_dal() + .is_verification_info_migration_performed() + .await?; + if !migration_performed { + tracing::info!(batch_size = %batch_size, "Running the storage migration for the contract verifier table"); + storage + .contract_verification_dal() + .perform_verification_info_migration(batch_size) + .await?; + } else { + tracing::info!("Storage migration is not needed"); + } + Ok(()) +} + #[tokio::main] async fn main() -> anyhow::Result<()> { let opt = Opt::parse(); @@ -51,6 +77,8 @@ async fn main() -> anyhow::Result<()> { .build() .await?; + perform_storage_migration(&pool).await?; + let (stop_sender, stop_receiver) = watch::channel(false); let contract_verifier = ContractVerifier::new(verifier_config.compilation_timeout(), pool) .await diff --git a/core/bin/verified_sources_fetcher/src/main.rs b/core/bin/verified_sources_fetcher/src/main.rs index 981eebf4a706..5ddf65fd580d 100644 --- a/core/bin/verified_sources_fetcher/src/main.rs +++ b/core/bin/verified_sources_fetcher/src/main.rs @@ -3,7 +3,7 @@ use std::io::Write; use zksync_config::configs::DatabaseSecrets; use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_env_config::FromEnv; -use zksync_types::contract_verification_api::SourceCodeData; +use zksync_types::contract_verification::api::SourceCodeData; #[tokio::main] async fn main() { diff --git a/core/lib/basic_types/src/bytecode.rs b/core/lib/basic_types/src/bytecode.rs index 12b4df69a6c6..a96c5b2b10cd 100644 --- a/core/lib/basic_types/src/bytecode.rs +++ b/core/lib/basic_types/src/bytecode.rs @@ -168,6 +168,14 @@ impl BytecodeMarker { } } +/// Removes padding from the bytecode, if necessary. +pub fn trim_bytecode(bytecode_hash: BytecodeHash, raw: &[u8]) -> anyhow::Result<&[u8]> { + match bytecode_hash.marker() { + BytecodeMarker::EraVm => Ok(raw), + BytecodeMarker::Evm => trim_padded_evm_bytecode(bytecode_hash, raw), + } +} + /// Removes padding from an EVM bytecode, returning the original EVM bytecode. pub fn trim_padded_evm_bytecode(bytecode_hash: BytecodeHash, raw: &[u8]) -> anyhow::Result<&[u8]> { if bytecode_hash.marker() != BytecodeMarker::Evm { diff --git a/core/lib/contract_verifier/src/compilers/mod.rs b/core/lib/contract_verifier/src/compilers/mod.rs index c82a6575ee4c..cbaf9d2225bc 100644 --- a/core/lib/contract_verifier/src/compilers/mod.rs +++ b/core/lib/contract_verifier/src/compilers/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use anyhow::Context as _; use serde::{Deserialize, Serialize}; -use zksync_types::contract_verification_api::CompilationArtifacts; +use zksync_types::contract_verification::api::CompilationArtifacts; pub(crate) use self::{ solc::{Solc, SolcInput}, diff --git a/core/lib/contract_verifier/src/compilers/solc.rs b/core/lib/contract_verifier/src/compilers/solc.rs index 10adcad3542e..4224e2a3dbcc 100644 --- a/core/lib/contract_verifier/src/compilers/solc.rs +++ b/core/lib/contract_verifier/src/compilers/solc.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, path::PathBuf, process::Stdio}; use anyhow::Context; use tokio::io::AsyncWriteExt; use zksync_queued_job_processor::async_trait; -use zksync_types::contract_verification_api::{ +use zksync_types::contract_verification::api::{ CompilationArtifacts, SourceCodeData, VerificationIncomingRequest, }; diff --git a/core/lib/contract_verifier/src/compilers/vyper.rs b/core/lib/contract_verifier/src/compilers/vyper.rs index 59b950f9f17f..46034a62e0a3 100644 --- a/core/lib/contract_verifier/src/compilers/vyper.rs +++ b/core/lib/contract_verifier/src/compilers/vyper.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, mem, path::PathBuf, process::Stdio}; use anyhow::Context; use tokio::io::AsyncWriteExt; use zksync_queued_job_processor::async_trait; -use zksync_types::contract_verification_api::{ +use zksync_types::contract_verification::api::{ CompilationArtifacts, SourceCodeData, VerificationIncomingRequest, }; diff --git a/core/lib/contract_verifier/src/compilers/zksolc.rs b/core/lib/contract_verifier/src/compilers/zksolc.rs index ff435e96aeb6..1b20f6d5d242 100644 --- a/core/lib/contract_verifier/src/compilers/zksolc.rs +++ b/core/lib/contract_verifier/src/compilers/zksolc.rs @@ -6,7 +6,7 @@ use semver::Version; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use zksync_queued_job_processor::async_trait; -use zksync_types::contract_verification_api::{ +use zksync_types::contract_verification::api::{ CompilationArtifacts, SourceCodeData, VerificationIncomingRequest, }; @@ -65,6 +65,7 @@ pub(crate) struct Optimizer { /// Whether the optimizer is enabled. pub enabled: bool, /// The optimization mode string. + #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, } @@ -144,12 +145,24 @@ impl ZkSolc { fn parse_single_file_yul_output( output: &str, ) -> Result { - let re = Regex::new(r"Contract `.*` bytecode: 0x([\da-f]+)").unwrap(); - let cap = re - .captures(output) - .context("Yul output doesn't match regex")?; + let cap = if output.contains("Binary:\n") { + // Format of the new output + // ======= /tmp/input.yul:Empty ======= + // Binary: + // 00000001002 <..> + let re = Regex::new(r"Binary:\n([\da-f]+)").unwrap(); + re.captures(output) + .with_context(|| format!("Yul output doesn't match regex. Output: {output}"))? + } else { + // Old compiler versions + let re_old = Regex::new(r"Contract `.*` bytecode: 0x([\da-f]+)").unwrap(); + re_old + .captures(output) + .with_context(|| format!("Yul output doesn't match regex. Output: {output}"))? + }; let bytecode_str = cap.get(1).context("no matches in Yul output")?.as_str(); let bytecode = hex::decode(bytecode_str).context("invalid Yul output bytecode")?; + Ok(CompilationArtifacts { bytecode, deployed_bytecode: None, @@ -255,6 +268,9 @@ impl Compiler for ZkSolc { .context("cannot create temporary Yul file")?; file.write_all(source_code.as_bytes()) .context("failed writing Yul file")?; + + // TODO: `zksolc` support standard JSON for `yul` since 1.5.0, so we don't have + // to parse `--bin` output. let child = command .arg(file.path().to_str().unwrap()) .arg("--optimization") diff --git a/core/lib/contract_verifier/src/compilers/zkvyper.rs b/core/lib/contract_verifier/src/compilers/zkvyper.rs index 4f7c10214f8a..4056736547f6 100644 --- a/core/lib/contract_verifier/src/compilers/zkvyper.rs +++ b/core/lib/contract_verifier/src/compilers/zkvyper.rs @@ -3,7 +3,7 @@ use std::{ffi::OsString, path, path::Path, process::Stdio}; use anyhow::Context as _; use tokio::{fs, io::AsyncWriteExt}; use zksync_queued_job_processor::async_trait; -use zksync_types::contract_verification_api::CompilationArtifacts; +use zksync_types::contract_verification::api::CompilationArtifacts; use super::VyperInput; use crate::{ diff --git a/core/lib/contract_verifier/src/lib.rs b/core/lib/contract_verifier/src/lib.rs index 43da4127b809..864c7b747d43 100644 --- a/core/lib/contract_verifier/src/lib.rs +++ b/core/lib/contract_verifier/src/lib.rs @@ -15,9 +15,12 @@ use zksync_dal::{contract_verification_dal::DeployedContractData, ConnectionPool use zksync_queued_job_processor::{async_trait, JobProcessor}; use zksync_types::{ bytecode::{trim_padded_evm_bytecode, BytecodeHash, BytecodeMarker}, - contract_verification_api::{ - self as api, CompilationArtifacts, VerificationIncomingRequest, VerificationInfo, - VerificationRequest, + contract_verification::{ + api::{ + self as api, CompilationArtifacts, VerificationIncomingRequest, VerificationInfo, + VerificationProblem, VerificationRequest, + }, + contract_identifier::{ContractIdentifier, Match}, }, Address, CONTRACT_DEPLOYER_ADDRESS, }; @@ -224,7 +227,7 @@ impl ContractVerifier { async fn verify( &self, mut request: VerificationRequest, - ) -> Result { + ) -> Result<(VerificationInfo, ContractIdentifier), ContractVerifierError> { // Bytecode should be present because it is checked when accepting request. let mut storage = self .connection_pool @@ -245,6 +248,8 @@ impl ContractVerifier { let bytecode_marker = BytecodeMarker::new(deployed_contract.bytecode_hash) .context("unknown bytecode kind")?; let artifacts = self.compile(request.req.clone(), bytecode_marker).await?; + let identifier = + ContractIdentifier::from_bytecode(bytecode_marker, artifacts.deployed_bytecode()); let constructor_args = match bytecode_marker { BytecodeMarker::EraVm => self .decode_era_vm_constructor_args(&deployed_contract, request.req.contract_address)?, @@ -265,14 +270,28 @@ impl ContractVerifier { .context("invalid stored EVM bytecode")?, }; - if artifacts.deployed_bytecode() != deployed_bytecode { - tracing::info!( - request_id = request.id, - deployed = hex::encode(deployed_bytecode), - compiled = hex::encode(artifacts.deployed_bytecode()), - "Deployed (runtime) bytecode mismatch", - ); - return Err(ContractVerifierError::BytecodeMismatch); + let mut verification_problems = Vec::new(); + + match identifier.matches(deployed_bytecode) { + Match::Full => {} + Match::Partial => { + tracing::trace!( + request_id = request.id, + deployed = hex::encode(deployed_bytecode), + compiled = hex::encode(artifacts.deployed_bytecode()), + "Partial bytecode match", + ); + verification_problems.push(VerificationProblem::IncorrectMetadata); + } + Match::None => { + tracing::trace!( + request_id = request.id, + deployed = hex::encode(deployed_bytecode), + compiled = hex::encode(artifacts.deployed_bytecode()), + "Deployed (runtime) bytecode mismatch", + ); + return Err(ContractVerifierError::BytecodeMismatch); + } } match constructor_args { @@ -284,6 +303,11 @@ impl ContractVerifier { hex::encode(&args), hex::encode(provided_constructor_args) ); + // We could, in theory, accept this contract and mark it as partially verified, + // but in during verification it is always possible to reconstruct the + // constructor arguments, so there is no reason for that. + // Mismatching constructor arguments are only needed for "similar bytecodes" + // (e.g. displayed contract as verified without a direct verification request). return Err(ContractVerifierError::IncorrectConstructorArguments); } } @@ -294,11 +318,13 @@ impl ContractVerifier { let verified_at = Utc::now(); tracing::trace!(%verified_at, "verified request"); - Ok(VerificationInfo { + let info = VerificationInfo { request, artifacts, verified_at, - }) + verification_problems, + }; + Ok((info, identifier)) } async fn compile_zksolc( @@ -544,17 +570,21 @@ impl ContractVerifier { async fn process_result( &self, request_id: usize, - verification_result: Result, + verification_result: Result<(VerificationInfo, ContractIdentifier), ContractVerifierError>, ) -> anyhow::Result<()> { let mut storage = self .connection_pool .connection_tagged("contract_verifier") .await?; match verification_result { - Ok(info) => { + Ok((info, identifier)) => { storage .contract_verification_dal() - .save_verification_info(info) + .save_verification_info( + info, + identifier.bytecode_keccak256, + identifier.bytecode_without_metadata_keccak256, + ) .await?; tracing::info!("Successfully processed request with id = {request_id}"); } diff --git a/core/lib/contract_verifier/src/resolver/mod.rs b/core/lib/contract_verifier/src/resolver/mod.rs index a9d2bcf9049d..b2bd659408d3 100644 --- a/core/lib/contract_verifier/src/resolver/mod.rs +++ b/core/lib/contract_verifier/src/resolver/mod.rs @@ -8,7 +8,7 @@ use std::{ use anyhow::Context as _; use tokio::fs; use zksync_queued_job_processor::async_trait; -use zksync_types::contract_verification_api::CompilationArtifacts; +use zksync_types::contract_verification::api::CompilationArtifacts; pub(crate) use self::{env::EnvCompilerResolver, github::GitHubCompilerResolver}; use crate::{ diff --git a/core/lib/contract_verifier/src/tests/mod.rs b/core/lib/contract_verifier/src/tests/mod.rs index 2ffb51ceb30a..31cec03e138e 100644 --- a/core/lib/contract_verifier/src/tests/mod.rs +++ b/core/lib/contract_verifier/src/tests/mod.rs @@ -9,7 +9,7 @@ use zksync_node_test_utils::{create_l1_batch, create_l2_block}; use zksync_types::{ address_to_h256, bytecode::{pad_evm_bytecode, BytecodeHash}, - contract_verification_api::{CompilerVersions, SourceCodeData, VerificationIncomingRequest}, + contract_verification::api::{CompilerVersions, SourceCodeData, VerificationIncomingRequest}, get_code_key, get_known_code_key, l2::L2Tx, tx::IncludedTxLocation, @@ -435,7 +435,7 @@ async fn contract_verifier_basics(contract: TestContract) { let (_stop_sender, stop_receiver) = watch::channel(false); verifier.run(stop_receiver, Some(1)).await.unwrap(); - assert_request_success(&mut storage, request_id, address, &expected_bytecode).await; + assert_request_success(&mut storage, request_id, address, &expected_bytecode, &[]).await; } async fn assert_request_success( @@ -443,6 +443,7 @@ async fn assert_request_success( request_id: usize, address: Address, expected_bytecode: &[u8], + verification_problems: &[VerificationProblem], ) -> VerificationInfo { let status = storage .contract_verification_dal() @@ -465,6 +466,11 @@ async fn assert_request_success( without_internal_types(verification_info.artifacts.abi.clone()), without_internal_types(counter_contract_abi()) ); + assert_eq!( + &verification_info.verification_problems, + verification_problems + ); + verification_info } @@ -541,7 +547,7 @@ async fn verifying_evm_bytecode(contract: TestContract) { let (_stop_sender, stop_receiver) = watch::channel(false); verifier.run(stop_receiver, Some(1)).await.unwrap(); - assert_request_success(&mut storage, request_id, address, &creation_bytecode).await; + assert_request_success(&mut storage, request_id, address, &creation_bytecode, &[]).await; } #[tokio::test] @@ -708,10 +714,12 @@ async fn creation_bytecode_mismatch() { .await .unwrap(); - let mock_resolver = MockCompilerResolver::solc(move |_| CompilationArtifacts { - bytecode: vec![4; 20], // differs from `creation_bytecode` - deployed_bytecode: Some(deployed_bytecode.clone()), - abi: counter_contract_abi(), + let mock_resolver = MockCompilerResolver::solc(move |_| { + CompilationArtifacts { + bytecode: vec![4; 20], // differs from `creation_bytecode` + deployed_bytecode: Some(deployed_bytecode.clone()), + abi: counter_contract_abi(), + } }); let verifier = ContractVerifier::with_resolver( Duration::from_secs(60), diff --git a/core/lib/contract_verifier/src/tests/real.rs b/core/lib/contract_verifier/src/tests/real.rs index ba7615528e15..d82edf7b0020 100644 --- a/core/lib/contract_verifier/src/tests/real.rs +++ b/core/lib/contract_verifier/src/tests/real.rs @@ -1,11 +1,18 @@ //! Tests using real compiler toolchains. Should be prepared by calling `zkstack contract-verifier init` //! with at least one `solc` and `zksolc` version. If there are no compilers, the tests will be ignored //! unless the `RUN_CONTRACT_VERIFICATION_TEST` env var is set to `true`, in which case the tests will fail. +//! +//! You can install the compilers to run these tests with the following command: +//! ``` +//! zkstack contract-verifier init --zksolc-version=v1.5.10 --zkvyper-version=v1.5.4 --solc-version=0.8.26 --vyper-version=v0.3.10 --era-vm-solc-version=0.8.26-1.0.1 --only +//! ``` use std::{env, sync::Arc, time::Duration}; use assert_matches::assert_matches; -use zksync_types::bytecode::validate_bytecode; +use zksync_types::{ + bytecode::validate_bytecode, contract_verification::contract_identifier::DetectedMetadata, +}; use super::*; @@ -19,38 +26,72 @@ impl Toolchain { const ALL: [Self; 2] = [Self::Solidity, Self::Vyper]; } +// The tests may expect specific compiler versions (e.g. contracts won't compile with Vyper 0.4.0), +// so we hardcode versions. +const ZKSOLC_VERSION: &str = "v1.5.10"; +const ERA_VM_SOLC_VERSION: &str = "0.8.26-1.0.1"; +const SOLC_VERSION: &str = "0.8.26"; +const VYPER_VERSION: &str = "v0.3.10"; +const ZKVYPER_VERSION: &str = "v1.5.4"; + #[derive(Debug, Clone)] struct TestCompilerVersions { solc: String, + eravm_solc: String, zksolc: String, vyper: String, zkvyper: String, } impl TestCompilerVersions { - fn new(versions: SupportedCompilerVersions) -> Option { - let solc = versions - .solc - .into_iter() - .find(|ver| !ver.starts_with("zkVM"))?; - Some(Self { - solc, - zksolc: versions.zksolc.into_iter().next()?, - vyper: versions.vyper.into_iter().next()?, - zkvyper: versions.zkvyper.into_iter().next()?, + fn new(versions: SupportedCompilerVersions) -> anyhow::Result { + // Stored compilers for our fork are prefixed with `zkVM-`. + let eravm_solc = format!("zkVM-{ERA_VM_SOLC_VERSION}"); + // Stored compilers for vyper do not have `v` prefix. + let vyper = VYPER_VERSION.strip_prefix("v").unwrap().to_owned(); + anyhow::ensure!( + versions.solc.contains(SOLC_VERSION), + "Expected solc version {SOLC_VERSION} to be installed, but it is not" + ); + anyhow::ensure!( + versions.solc.contains(&eravm_solc), + "Expected era-vm solc version {ERA_VM_SOLC_VERSION} to be installed, but it is not" + ); + anyhow::ensure!( + versions.zksolc.contains(ZKSOLC_VERSION), + "Expected zksolc version {ZKSOLC_VERSION} to be installed, but it is not" + ); + anyhow::ensure!( + versions.vyper.contains(&vyper), + "Expected vyper version {VYPER_VERSION} to be installed, but it is not" + ); + anyhow::ensure!( + versions.zkvyper.contains(ZKVYPER_VERSION), + "Expected zkvyper version {ZKVYPER_VERSION} to be installed, but it is not" + ); + + Ok(Self { + solc: SOLC_VERSION.to_owned(), + eravm_solc, + zksolc: ZKSOLC_VERSION.to_owned(), + vyper, + zkvyper: ZKVYPER_VERSION.to_owned(), }) } fn zksolc(self) -> ZkCompilerVersions { ZkCompilerVersions { - base: self.solc, + base: self.eravm_solc, zk: self.zksolc, } } fn solc_for_api(self, bytecode_kind: BytecodeMarker) -> CompilerVersions { CompilerVersions::Solc { - compiler_solc_version: self.solc, + compiler_solc_version: match bytecode_kind { + BytecodeMarker::Evm => self.solc, + BytecodeMarker::EraVm => self.eravm_solc, + }, compiler_zksolc_version: match bytecode_kind { BytecodeMarker::Evm => None, BytecodeMarker::EraVm => Some(self.zksolc), @@ -76,32 +117,39 @@ impl TestCompilerVersions { } } -async fn checked_env_resolver() -> Option<(EnvCompilerResolver, TestCompilerVersions)> { +async fn checked_env_resolver() -> anyhow::Result<(EnvCompilerResolver, TestCompilerVersions)> { let compiler_resolver = EnvCompilerResolver::default(); - let supported_compilers = compiler_resolver.supported_versions().await.ok()?; - Some(( + let supported_compilers = compiler_resolver.supported_versions().await?; + Ok(( compiler_resolver, TestCompilerVersions::new(supported_compilers)?, )) } -fn assert_no_compilers_expected() { +fn assert_no_compilers_expected(err: anyhow::Error) { + let error_message = format!( + "Expected pre-installed compilers since `RUN_CONTRACT_VERIFICATION_TEST=true`, but at least one compiler is not installed.\n \ + Detail: {}\n\n \ + Use the following command to install compilers:\n \ + zkstack contract-verifier init --zksolc-version={} --zkvyper-version={} --solc-version={} --vyper-version={} --era-vm-solc-version={} --only", + err, ZKSOLC_VERSION, ZKVYPER_VERSION, SOLC_VERSION, VYPER_VERSION, ERA_VM_SOLC_VERSION + ); + assert_ne!( env::var("RUN_CONTRACT_VERIFICATION_TEST").ok().as_deref(), Some("true"), - "Expected pre-installed compilers since `RUN_CONTRACT_VERIFICATION_TEST=true`, but they are not installed. \ - Use `zkstack contract-verifier init` to install compilers" + "{error_message}" ); - println!("No compilers found, skipping the test"); + println!("At least one compiler is not found, skipping the test"); } /// Simplifies initializing real compiler resolver in tests. macro_rules! real_resolver { () => { match checked_env_resolver().await { - Some(resolver_and_versions) => resolver_and_versions, - None => { - assert_no_compilers_expected(); + Ok(resolver_and_versions) => resolver_and_versions, + Err(err) => { + assert_no_compilers_expected(err); return; } } @@ -254,10 +302,16 @@ async fn compiling_yul_with_zksolc() { let req = test_yul_request(supported_compilers.solc_for_api(BytecodeMarker::EraVm)); let input = ZkSolc::build_input(req).unwrap(); let output = compiler.compile(input).await.unwrap(); + let identifier = + ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, output.deployed_bytecode()); assert!(!output.bytecode.is_empty()); assert!(output.deployed_bytecode.is_none()); assert_eq!(output.abi, serde_json::json!([])); + assert_matches!( + identifier.detected_metadata, + Some(DetectedMetadata::Keccak256) + ); } #[tokio::test] @@ -272,10 +326,17 @@ async fn compiling_standalone_yul() { }); let input = Solc::build_input(req).unwrap(); let output = compiler.compile(input).await.unwrap(); + let identifier = + ContractIdentifier::from_bytecode(BytecodeMarker::Evm, output.deployed_bytecode()); assert!(!output.bytecode.is_empty()); assert_ne!(output.deployed_bytecode.unwrap(), output.bytecode); assert_eq!(output.abi, serde_json::json!([])); + assert_matches!( + identifier.detected_metadata, + None, + "No metadata for compiler yul for EVM" + ); } fn test_vyper_request( @@ -322,9 +383,15 @@ async fn using_real_zkvyper(specify_contract_file: bool) { ); let input = VyperInput::new(req).unwrap(); let output = compiler.compile(input).await.unwrap(); + let identifier = + ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, output.deployed_bytecode()); validate_bytecode(&output.bytecode).unwrap(); assert_eq!(output.abi, without_internal_types(counter_contract_abi())); + assert_matches!( + identifier.detected_metadata, + Some(DetectedMetadata::Keccak256) + ); } #[test_casing(2, [false, true])] @@ -347,9 +414,13 @@ async fn using_standalone_vyper(specify_contract_file: bool) { ); let input = VyperInput::new(req).unwrap(); let output = compiler.compile(input).await.unwrap(); + let identifier = + ContractIdentifier::from_bytecode(BytecodeMarker::Evm, output.deployed_bytecode()); assert!(output.deployed_bytecode.is_some()); assert_eq!(output.abi, without_internal_types(counter_contract_abi())); + // Vyper does not provide metadata for bytecode. + assert_matches!(identifier.detected_metadata, None); } #[tokio::test] @@ -367,9 +438,13 @@ async fn using_standalone_vyper_without_optimization() { req.optimization_used = false; let input = VyperInput::new(req).unwrap(); let output = compiler.compile(input).await.unwrap(); + let identifier = + ContractIdentifier::from_bytecode(BytecodeMarker::Evm, output.deployed_bytecode()); assert!(output.deployed_bytecode.is_some()); assert_eq!(output.abi, without_internal_types(counter_contract_abi())); + // Vyper does not provide metadata for bytecode. + assert_matches!(identifier.detected_metadata, None); } #[tokio::test] @@ -469,6 +544,30 @@ async fn using_real_compiler_in_verifier(bytecode_kind: BytecodeMarker, toolchai compiler.compile(input).await.unwrap() } }; + let identifier = ContractIdentifier::from_bytecode(bytecode_kind, output.deployed_bytecode()); + + match (bytecode_kind, toolchain) { + (BytecodeMarker::Evm, Toolchain::Vyper) => { + assert!( + identifier.detected_metadata.is_none(), + "No metadata for EVM Vyper" + ); + } + (BytecodeMarker::Evm, Toolchain::Solidity) => { + assert_matches!( + identifier.detected_metadata, + Some(DetectedMetadata::Cbor), + "Cbor metadata for EVM Solidity by default" + ); + } + (BytecodeMarker::EraVm, _) => { + assert_matches!( + identifier.detected_metadata, + Some(DetectedMetadata::Keccak256), + "Keccak256 metadata for EraVM by default" + ); + } + } let pool = ConnectionPool::test_pool().await; let mut storage = pool.connection().await.unwrap(); @@ -505,7 +604,165 @@ async fn using_real_compiler_in_verifier(bytecode_kind: BytecodeMarker, toolchai let (_stop_sender, stop_receiver) = watch::channel(false); verifier.run(stop_receiver, Some(1)).await.unwrap(); - assert_request_success(&mut storage, request_id, address, &output.bytecode).await; + assert_request_success(&mut storage, request_id, address, &output.bytecode, &[]).await; +} + +#[test_casing(2, [false, true])] +#[tokio::test] +async fn using_zksolc_partial_match(use_cbor: bool) { + let (compiler_resolver, supported_compilers) = real_resolver!(); + + let mut req: VerificationIncomingRequest = VerificationIncomingRequest { + compiler_versions: supported_compilers + .clone() + .solc_for_api(BytecodeMarker::EraVm), + ..test_request(Address::repeat_byte(1), COUNTER_CONTRACT) + }; + let hash_type = if use_cbor { "ipfs" } else { "keccak256" }; + // We need to manually construct the input, since `SolSingleFile` doesn't let us specify metadata hash type. + // Note: prior to 1.5.7 field was named `bytecodeHash`. + req.source_code_data = SourceCodeData::StandardJsonInput( + serde_json::json!({ + "language": "Solidity", + "sources": { + "Counter.sol": { + "content": COUNTER_CONTRACT, + }, + }, + "settings": { + "outputSelection": { + "*": { + "": [ "abi" ], + "*": [ "abi" ] + } + }, + "isSystem": false, + "forceEvmla": false, + "metadata": { + "hashType": hash_type + }, + "optimizer": { + "enabled": true + } + } + }) + .as_object() + .unwrap() + .clone(), + ); + let contract_name = req.contract_name.clone(); + let address = Address::repeat_byte(1); + let compiler = compiler_resolver + .resolve_zksolc(&supported_compilers.clone().zksolc()) + .await + .unwrap(); + let input_for_request = ZkSolc::build_input(req.clone()).unwrap(); + + let output_for_request = compiler.compile(input_for_request).await.unwrap(); + let identifier_for_request = ContractIdentifier::from_bytecode( + BytecodeMarker::EraVm, + output_for_request.deployed_bytecode(), + ); + + // Now prepare data for contract verification storage (with different metadata). + let compiler = compiler_resolver + .resolve_zksolc(&supported_compilers.zksolc()) + .await + .unwrap(); + let mut input_for_storage = ZkSolc::build_input(req.clone()).unwrap(); + // Change the source file name. + if let ZkSolcInput::StandardJson { + input, file_name, .. + } = &mut input_for_storage + { + let source = input + .sources + .remove(&format!("{contract_name}.sol")) + .unwrap(); + let new_file_name = "random_name.sol".to_owned(); + input.sources.insert(new_file_name.clone(), source); + *file_name = new_file_name; + if use_cbor { + input.settings.other.as_object_mut().unwrap().insert( + "metadata".to_string(), + serde_json::json!({ "hashType": "ipfs"}), + ); + } + } else { + panic!("unexpected input: {input_for_storage:?}"); + } + + let output_for_storage = compiler.compile(input_for_storage).await.unwrap(); + let identifier_for_storage = ContractIdentifier::from_bytecode( + BytecodeMarker::EraVm, + output_for_storage.deployed_bytecode(), + ); + + assert_eq!( + identifier_for_request.matches(output_for_storage.deployed_bytecode()), + Match::Partial, + "must be a partial match (1)" + ); + assert_eq!( + identifier_for_storage.matches(output_for_request.deployed_bytecode()), + Match::Partial, + "must be a partial match (2)" + ); + if use_cbor { + assert_matches!( + identifier_for_request.detected_metadata, + Some(DetectedMetadata::Cbor) + ); + assert_matches!( + identifier_for_storage.detected_metadata, + Some(DetectedMetadata::Cbor) + ); + } else { + assert_matches!( + identifier_for_request.detected_metadata, + Some(DetectedMetadata::Keccak256) + ); + assert_matches!( + identifier_for_storage.detected_metadata, + Some(DetectedMetadata::Keccak256) + ); + } + + let pool = ConnectionPool::test_pool().await; + let mut storage = pool.connection().await.unwrap(); + prepare_storage(&mut storage).await; + mock_deployment( + &mut storage, + address, + output_for_storage.bytecode.clone(), + &[], + ) + .await; + let request_id = storage + .contract_verification_dal() + .add_contract_verification_request(&req) + .await + .unwrap(); + + let verifier = ContractVerifier::with_resolver( + Duration::from_secs(60), + pool.clone(), + Arc::new(compiler_resolver), + ) + .await + .unwrap(); + + let (_stop_sender, stop_receiver) = watch::channel(false); + verifier.run(stop_receiver, Some(1)).await.unwrap(); + + assert_request_success( + &mut storage, + request_id, + address, + &output_for_request.bytecode, + &[VerificationProblem::IncorrectMetadata], + ) + .await; } #[test_casing(2, BYTECODE_KINDS)] diff --git a/core/lib/dal/.sqlx/query-1823e1ac602ce4ba1db06543af9cb2685cda1ae7ecca83062ede7320c3b4a427.json b/core/lib/dal/.sqlx/query-1823e1ac602ce4ba1db06543af9cb2685cda1ae7ecca83062ede7320c3b4a427.json deleted file mode 100644 index 1e20a9151b98..000000000000 --- a/core/lib/dal/.sqlx/query-1823e1ac602ce4ba1db06543af9cb2685cda1ae7ecca83062ede7320c3b4a427.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO\n contracts_verification_info (address, verification_info)\n VALUES\n ($1, $2)\n ON CONFLICT (address) DO\n UPDATE\n SET\n verification_info = $2\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Bytea", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "1823e1ac602ce4ba1db06543af9cb2685cda1ae7ecca83062ede7320c3b4a427" -} diff --git a/core/lib/dal/.sqlx/query-2d0c2e9ec4187641baef8a33229bffc78d92adb3c1e3ca60b12163e38c67047e.json b/core/lib/dal/.sqlx/query-2d0c2e9ec4187641baef8a33229bffc78d92adb3c1e3ca60b12163e38c67047e.json deleted file mode 100644 index f61f39e3b0b0..000000000000 --- a/core/lib/dal/.sqlx/query-2d0c2e9ec4187641baef8a33229bffc78d92adb3c1e3ca60b12163e38c67047e.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n COUNT(*) AS \"count!\"\n FROM\n contracts_verification_info\n WHERE\n address = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Bytea" - ] - }, - "nullable": [ - null - ] - }, - "hash": "2d0c2e9ec4187641baef8a33229bffc78d92adb3c1e3ca60b12163e38c67047e" -} diff --git a/core/lib/dal/.sqlx/query-349d41c8ce192e82152e9d254c23ed5f1e6eac5b71232c784abd4d4cd8677805.json b/core/lib/dal/.sqlx/query-349d41c8ce192e82152e9d254c23ed5f1e6eac5b71232c784abd4d4cd8677805.json new file mode 100644 index 000000000000..2de71e3d1ab5 --- /dev/null +++ b/core/lib/dal/.sqlx/query-349d41c8ce192e82152e9d254c23ed5f1e6eac5b71232c784abd4d4cd8677805.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO\n contract_verification_info_v2 (\n initial_contract_addr,\n bytecode_keccak256,\n bytecode_without_metadata_keccak256,\n verification_info\n )\n VALUES\n ($1, $2, $3, $4)\n ON CONFLICT (initial_contract_addr) DO\n UPDATE\n SET\n bytecode_keccak256 = $2,\n bytecode_without_metadata_keccak256 = $3,\n verification_info = $4\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bytea", + "Bytea", + "Bytea", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "349d41c8ce192e82152e9d254c23ed5f1e6eac5b71232c784abd4d4cd8677805" +} diff --git a/core/lib/dal/.sqlx/query-668cf72b78c6071340143ba9498046b7820e39b63ed4f98bcaa3b3f305cbe576.json b/core/lib/dal/.sqlx/query-668cf72b78c6071340143ba9498046b7820e39b63ed4f98bcaa3b3f305cbe576.json new file mode 100644 index 000000000000..9dffecdc4c19 --- /dev/null +++ b/core/lib/dal/.sqlx/query-668cf72b78c6071340143ba9498046b7820e39b63ed4f98bcaa3b3f305cbe576.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n (SELECT COUNT(*) FROM contracts_verification_info) AS count_v1,\n (SELECT COUNT(*) FROM contract_verification_info_v2) AS count_v2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count_v1", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "count_v2", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null, + null + ] + }, + "hash": "668cf72b78c6071340143ba9498046b7820e39b63ed4f98bcaa3b3f305cbe576" +} diff --git a/core/lib/dal/.sqlx/query-6cb50a8fbe1341ba7ea496bb0f2072dcee6e6f8439e6b43eebd6df5563a4d0b9.json b/core/lib/dal/.sqlx/query-6cb50a8fbe1341ba7ea496bb0f2072dcee6e6f8439e6b43eebd6df5563a4d0b9.json new file mode 100644 index 000000000000..a78dcf480064 --- /dev/null +++ b/core/lib/dal/.sqlx/query-6cb50a8fbe1341ba7ea496bb0f2072dcee6e6f8439e6b43eebd6df5563a4d0b9.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n verification_info,\n bytecode_keccak256,\n bytecode_without_metadata_keccak256\n FROM\n contract_verification_info_v2\n WHERE\n bytecode_keccak256 = $1\n OR\n (\n bytecode_without_metadata_keccak256 IS NOT null\n AND bytecode_without_metadata_keccak256 = $2\n )\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "verification_info", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "bytecode_keccak256", + "type_info": "Bytea" + }, + { + "ordinal": 2, + "name": "bytecode_without_metadata_keccak256", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Bytea", + "Bytea" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "6cb50a8fbe1341ba7ea496bb0f2072dcee6e6f8439e6b43eebd6df5563a4d0b9" +} diff --git a/core/lib/dal/.sqlx/query-a331b209eafd82595ad75e24135989ff52100e6a93537c35961e62268d7cd26e.json b/core/lib/dal/.sqlx/query-a331b209eafd82595ad75e24135989ff52100e6a93537c35961e62268d7cd26e.json new file mode 100644 index 000000000000..7d2121d708f3 --- /dev/null +++ b/core/lib/dal/.sqlx/query-a331b209eafd82595ad75e24135989ff52100e6a93537c35961e62268d7cd26e.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n address,\n verification_info::text AS verification_info\n FROM\n contracts_verification_info\n WHERE address > $1\n ORDER BY\n address\n LIMIT $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "address", + "type_info": "Bytea" + }, + { + "ordinal": 1, + "name": "verification_info", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Bytea", + "Int8" + ] + }, + "nullable": [ + false, + null + ] + }, + "hash": "a331b209eafd82595ad75e24135989ff52100e6a93537c35961e62268d7cd26e" +} diff --git a/core/lib/dal/.sqlx/query-ca2c63a0e25406eec4e92a7cfda80a651e28abb3dd3582211ceb6b2bb8009258.json b/core/lib/dal/.sqlx/query-ca2c63a0e25406eec4e92a7cfda80a651e28abb3dd3582211ceb6b2bb8009258.json new file mode 100644 index 000000000000..2708c5b78701 --- /dev/null +++ b/core/lib/dal/.sqlx/query-ca2c63a0e25406eec4e92a7cfda80a651e28abb3dd3582211ceb6b2bb8009258.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n COUNT(*)\n FROM\n contract_verification_info_v2 v2\n JOIN contracts_verification_info v1 ON initial_contract_addr = address\n WHERE v1.verification_info::text != v2.verification_info::text\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "ca2c63a0e25406eec4e92a7cfda80a651e28abb3dd3582211ceb6b2bb8009258" +} diff --git a/core/lib/dal/.sqlx/query-daa2ad4ebde17808e059aa6bcf148743ffed494316750be3b0ffb10b2fc09e93.json b/core/lib/dal/.sqlx/query-daa2ad4ebde17808e059aa6bcf148743ffed494316750be3b0ffb10b2fc09e93.json new file mode 100644 index 000000000000..fd279a570be9 --- /dev/null +++ b/core/lib/dal/.sqlx/query-daa2ad4ebde17808e059aa6bcf148743ffed494316750be3b0ffb10b2fc09e93.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n verification_info\n FROM\n contract_verification_info_v2\n WHERE\n initial_contract_addr = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "verification_info", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Bytea" + ] + }, + "nullable": [ + false + ] + }, + "hash": "daa2ad4ebde17808e059aa6bcf148743ffed494316750be3b0ffb10b2fc09e93" +} diff --git a/core/lib/dal/Cargo.toml b/core/lib/dal/Cargo.toml index 4b093dd181bb..80cd955ff298 100644 --- a/core/lib/dal/Cargo.toml +++ b/core/lib/dal/Cargo.toml @@ -53,6 +53,7 @@ hex.workspace = true strum = { workspace = true, features = ["derive"] } tracing.workspace = true chrono = { workspace = true, features = ["serde"] } +rayon.workspace = true [dev-dependencies] zksync_test_contracts.workspace = true diff --git a/core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.down.sql b/core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.down.sql new file mode 100644 index 000000000000..03c94eab5f61 --- /dev/null +++ b/core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.down.sql @@ -0,0 +1,3 @@ +DROP INDEX IF EXISTS contract_verification_info_v2_bytecode_keccak256_idx; +DROP INDEX IF EXISTS contract_verification_info_v2_bytecode_without_metadata_keccak256_idx; +DROP TABLE IF EXISTS contract_verification_info_v2; diff --git a/core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.up.sql b/core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.up.sql new file mode 100644 index 000000000000..c7eee9063221 --- /dev/null +++ b/core/lib/dal/migrations/20250122102800_contract-verifier-new-schema.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS contract_verification_info_v2 ( + initial_contract_addr BYTEA NOT NULL PRIMARY KEY, + bytecode_keccak256 BYTEA NOT NULL, + bytecode_without_metadata_keccak256 BYTEA NOT NULL, + verification_info JSONB NOT NULL, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Add hash indexes for hash columns +CREATE INDEX IF NOT EXISTS contract_verification_info_v2_bytecode_keccak256_idx ON contract_verification_info_v2 (bytecode_keccak256); +CREATE INDEX IF NOT EXISTS contract_verification_info_v2_bytecode_without_metadata_keccak256_idx ON contract_verification_info_v2 (bytecode_without_metadata_keccak256); diff --git a/core/lib/dal/src/contract_verification_dal.rs b/core/lib/dal/src/contract_verification_dal.rs index 57bea5392cf8..9125f972c55b 100644 --- a/core/lib/dal/src/contract_verification_dal.rs +++ b/core/lib/dal/src/contract_verification_dal.rs @@ -5,13 +5,20 @@ use std::{ time::Duration, }; +use rayon::prelude::*; use sqlx::postgres::types::PgInterval; -use zksync_db_connection::{error::SqlxContext, instrument::InstrumentExt}; +use zksync_db_connection::{ + error::SqlxContext, + instrument::{CopyStatement, InstrumentExt}, +}; use zksync_types::{ address_to_h256, - contract_verification_api::{ - VerificationIncomingRequest, VerificationInfo, VerificationRequest, - VerificationRequestStatus, + contract_verification::{ + api::{ + VerificationIncomingRequest, VerificationInfo, VerificationRequest, + VerificationRequestStatus, + }, + contract_identifier::ContractIdentifier, }, web3, Address, CONTRACT_DEPLOYER_ADDRESS, H256, }; @@ -188,6 +195,8 @@ impl ContractVerificationDal<'_, '_> { pub async fn save_verification_info( &mut self, verification_info: VerificationInfo, + bytecode_keccak256: H256, + bytecode_without_metadata_keccak256: H256, ) -> DalResult<()> { let mut transaction = self.storage.start_transaction().await?; let id = verification_info.request.id; @@ -216,15 +225,24 @@ impl ContractVerificationDal<'_, '_> { sqlx::query!( r#" INSERT INTO - contracts_verification_info (address, verification_info) + contract_verification_info_v2 ( + initial_contract_addr, + bytecode_keccak256, + bytecode_without_metadata_keccak256, + verification_info + ) VALUES - ($1, $2) - ON CONFLICT (address) DO + ($1, $2, $3, $4) + ON CONFLICT (initial_contract_addr) DO UPDATE SET - verification_info = $2 + bytecode_keccak256 = $2, + bytecode_without_metadata_keccak256 = $3, + verification_info = $4 "#, address.as_bytes(), + bytecode_keccak256.as_bytes(), + bytecode_without_metadata_keccak256.as_bytes(), &verification_info_json ) .instrument("save_verification_info#insert") @@ -376,27 +394,6 @@ impl ContractVerificationDal<'_, '_> { .await } - /// Returns true if the contract has a stored contracts_verification_info. - pub async fn is_contract_verified(&mut self, address: Address) -> DalResult { - let count = sqlx::query!( - r#" - SELECT - COUNT(*) AS "count!" - FROM - contracts_verification_info - WHERE - address = $1 - "#, - address.as_bytes() - ) - .instrument("is_contract_verified") - .with_arg("address", &address) - .fetch_one(self.storage) - .await? - .count; - Ok(count > 0) - } - async fn get_compiler_versions(&mut self, compiler: Compiler) -> DalResult> { let compiler = format!("{compiler}"); let versions: Vec<_> = sqlx::query!( @@ -537,6 +534,29 @@ impl ContractVerificationDal<'_, '_> { pub async fn get_contract_verification_info( &mut self, address: Address, + ) -> anyhow::Result> { + // Do everything in a read-only transaction for a consistent view. + let mut transaction = self + .storage + .transaction_builder()? + .set_readonly() + .build() + .await?; + + let mut dal = ContractVerificationDal { + storage: &mut transaction, + }; + let info = if dal.is_verification_info_migration_performed().await? { + dal.get_contract_verification_info_v2(address).await? + } else { + dal.get_contract_verification_info_v1(address).await? + }; + Ok(info) + } + + async fn get_contract_verification_info_v1( + &mut self, + address: Address, ) -> DalResult> { Ok(sqlx::query!( r#" @@ -560,6 +580,236 @@ impl ContractVerificationDal<'_, '_> { .await? .flatten()) } + + async fn get_contract_verification_info_v2( + &mut self, + address: Address, + ) -> anyhow::Result> { + Ok(sqlx::query!( + r#" + SELECT + verification_info + FROM + contract_verification_info_v2 + WHERE + initial_contract_addr = $1 + "#, + address.as_bytes(), + ) + .try_map(|row| { + serde_json::from_value(row.verification_info).decode_column("verification_info") + }) + .instrument("get_contract_verification_info_v2") + .with_arg("address", &address) + .fetch_optional(self.storage) + .await? + .flatten()) + } + + pub async fn get_partial_match_verification_info( + &mut self, + bytecode_keccak256: H256, + bytecode_without_metadata_keccak256: H256, + ) -> DalResult> { + sqlx::query!( + r#" + SELECT + verification_info, + bytecode_keccak256, + bytecode_without_metadata_keccak256 + FROM + contract_verification_info_v2 + WHERE + bytecode_keccak256 = $1 + OR + ( + bytecode_without_metadata_keccak256 IS NOT null + AND bytecode_without_metadata_keccak256 = $2 + ) + "#, + bytecode_keccak256.as_bytes(), + bytecode_without_metadata_keccak256.as_bytes() + ) + .try_map(|row| { + let info = serde_json::from_value::(row.verification_info) + .decode_column("verification_info")?; + let bytecode_keccak256 = H256::from_slice(&row.bytecode_keccak256); + let bytecode_without_metadata_keccak256 = + H256::from_slice(&row.bytecode_without_metadata_keccak256); + Ok(( + info, + bytecode_keccak256, + bytecode_without_metadata_keccak256, + )) + }) + .instrument("get_partial_match_verification_info") + .with_arg("bytecode_keccak256", &bytecode_keccak256) + .with_arg( + "bytecode_without_metadata_keccak256", + &bytecode_without_metadata_keccak256, + ) + .fetch_optional(self.storage) + .await + } + + /// Checks if migration from `contracts_verification_info` to `contract_verification_info_v2` is performed + /// by checking if the latter has more or equal number of rows. + pub async fn is_verification_info_migration_performed(&mut self) -> DalResult { + let row = sqlx::query!( + r#" + SELECT + (SELECT COUNT(*) FROM contracts_verification_info) AS count_v1, + (SELECT COUNT(*) FROM contract_verification_info_v2) AS count_v2 + "#, + ) + .instrument("is_verification_info_migration_performed") + .fetch_one(self.storage) + .await?; + + Ok(row.count_v2 >= row.count_v1) + } + + pub async fn perform_verification_info_migration( + &mut self, + batch_size: usize, + ) -> anyhow::Result<()> { + // We use a long-running transaction, since the migration is one-time and during it + // no writes are expected to the tables, so locked rows are not a problem. + let mut transaction = self.storage.start_transaction().await?; + + // Offset is a number of already migrated contracts. + let mut offset = 0usize; + let mut cursor = vec![]; + loop { + let cursor_str = format!("0x{}", hex::encode(&cursor)); + + // Fetch JSON as text to avoid roundtrip through `serde_json::Value`, as it's super slow. + let (addresses, verification_infos): (Vec>, Vec) = sqlx::query!( + r#" + SELECT + address, + verification_info::text AS verification_info + FROM + contracts_verification_info + WHERE address > $1 + ORDER BY + address + LIMIT $2 + "#, + &cursor, + batch_size as i64, + ) + .instrument("perform_verification_info_migration#select") + .with_arg("cursor", &cursor_str) + .with_arg("batch_size", &batch_size) + .fetch_all(&mut transaction) + .await? + .into_iter() + .filter_map(|row| row.verification_info.map(|info| (row.address, info))) + .collect(); + + if addresses.is_empty() { + tracing::info!("No more contracts to process"); + break; + } + + tracing::info!( + "Processing {} contracts (processed: {offset}); cursor {cursor_str}", + addresses.len() + ); + + let ids: Vec = (0..addresses.len()) + .into_par_iter() + .map(|idx| { + let address = &addresses[idx]; + let info_json = &verification_infos[idx]; + let verification_info = serde_json::from_str::(info_json) + .unwrap_or_else(|err| { + panic!( + "Malformed data in DB, address {}, data: {info_json}, error: {err}", + hex::encode(address) + ); + }); + ContractIdentifier::from_bytecode( + verification_info.bytecode_marker(), + verification_info.artifacts.deployed_bytecode(), + ) + }) + .collect(); + + let now = chrono::Utc::now().naive_utc().to_string(); + let mut buffer = String::new(); + for idx in 0..addresses.len() { + let address = hex::encode(&addresses[idx]); + let bytecode_keccak256 = hex::encode(ids[idx].bytecode_keccak256); + let bytecode_without_metadata_keccak256 = + hex::encode(ids[idx].bytecode_without_metadata_keccak256); + let verification_info = verification_infos[idx].replace('"', r#""""#); + + let row = format!( + r#"\\x{initial_contract_addr},\\x{bytecode_keccak256},\\x{bytecode_without_metadata_keccak256},"{verification_info}",{created_at},{updated_at}"#, + initial_contract_addr = address, + bytecode_keccak256 = bytecode_keccak256, + bytecode_without_metadata_keccak256 = bytecode_without_metadata_keccak256, + verification_info = verification_info, + created_at = now, + updated_at = now + ); + buffer.push_str(&row); + buffer.push('\n'); + } + + let contracts_len = addresses.len(); + let copy = CopyStatement::new( + "COPY contract_verification_info_v2( + initial_contract_addr, + bytecode_keccak256, + bytecode_without_metadata_keccak256, + verification_info, + created_at, + updated_at + ) FROM STDIN (FORMAT CSV, NULL 'null', DELIMITER ',')", + ) + .instrument("perform_verification_info_migration#copy") + .with_arg("cursor", &cursor_str) + .with_arg("contracts.len", &contracts_len) + .start(&mut transaction) + .await?; + + copy.send(buffer.as_bytes()).await?; + + offset += batch_size; + cursor = addresses.last().unwrap().clone(); + } + + // Sanity check. + tracing::info!("All the rows are migrated, verifying the migration"); + let count_unequal = sqlx::query!( + r#" + SELECT + COUNT(*) + FROM + contract_verification_info_v2 v2 + JOIN contracts_verification_info v1 ON initial_contract_addr = address + WHERE v1.verification_info::text != v2.verification_info::text + "#, + ) + .instrument("is_verification_info_migration_performed") + .fetch_one(&mut transaction) + .await? + .count + .unwrap(); + if count_unequal > 0 { + anyhow::bail!( + "Migration failed: {} rows have different data in the new table", + count_unequal + ); + } + + tracing::info!("Migration is successful, committing the transaction"); + transaction.commit().await?; + Ok(()) + } } #[cfg(test)] @@ -568,7 +818,7 @@ mod tests { use zksync_types::{ bytecode::BytecodeHash, - contract_verification_api::{CompilerVersions, SourceCodeData}, + contract_verification::api::{CompilerVersions, SourceCodeData}, tx::IncludedTxLocation, Execute, L1BatchNumber, L2BlockNumber, ProtocolVersion, }; diff --git a/core/lib/dal/src/models/storage_verification_request.rs b/core/lib/dal/src/models/storage_verification_request.rs index ae4718e41290..1ea70ed38129 100644 --- a/core/lib/dal/src/models/storage_verification_request.rs +++ b/core/lib/dal/src/models/storage_verification_request.rs @@ -1,5 +1,5 @@ use zksync_types::{ - contract_verification_api::{ + contract_verification::api::{ CompilerType, CompilerVersions, SourceCodeData, VerificationIncomingRequest, VerificationRequest, }, diff --git a/core/lib/types/Cargo.toml b/core/lib/types/Cargo.toml index 6af0e39d14f0..f4eeebfce038 100644 --- a/core/lib/types/Cargo.toml +++ b/core/lib/types/Cargo.toml @@ -29,6 +29,7 @@ rlp.workspace = true serde.workspace = true serde_json.workspace = true serde_with = { workspace = true, features = ["hex"] } +ciborium.workspace = true bigdecimal.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true diff --git a/core/lib/types/src/contract_verification_api.rs b/core/lib/types/src/contract_verification/api.rs similarity index 92% rename from core/lib/types/src/contract_verification_api.rs rename to core/lib/types/src/contract_verification/api.rs index cca5ae5a83a0..594596070d9f 100644 --- a/core/lib/types/src/contract_verification_api.rs +++ b/core/lib/types/src/contract_verification/api.rs @@ -5,6 +5,7 @@ use serde::{ de::{Deserializer, Error, MapAccess, Unexpected, Visitor}, Deserialize, Serialize, }; +use zksync_basic_types::bytecode::BytecodeMarker; pub use crate::Execute as ExecuteData; use crate::{web3::Bytes, Address}; @@ -232,12 +233,37 @@ impl CompilationArtifacts { } } +/// Non-critical issues detected during verification. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum VerificationProblem { + /// The bytecode is correct, but metadata hash is different. + IncorrectMetadata, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VerificationInfo { pub request: VerificationRequest, pub artifacts: CompilationArtifacts, pub verified_at: DateTime, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub verification_problems: Vec, +} + +impl VerificationInfo { + pub fn is_perfect_match(&self) -> bool { + self.verification_problems.is_empty() + } + + pub fn bytecode_marker(&self) -> BytecodeMarker { + // Deployed bytecode is only present for EVM contracts. + if self.artifacts.deployed_bytecode.is_some() { + BytecodeMarker::Evm + } else { + BytecodeMarker::EraVm + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/core/lib/types/src/contract_verification/contract_identifier.rs b/core/lib/types/src/contract_verification/contract_identifier.rs new file mode 100644 index 000000000000..354ddeaa0e29 --- /dev/null +++ b/core/lib/types/src/contract_verification/contract_identifier.rs @@ -0,0 +1,396 @@ +use serde::{Deserialize, Serialize}; + +use crate::{bytecode::BytecodeMarker, web3::keccak256, H256}; + +/// An identifier of the contract bytecode. +/// This identifier can be used to detect different contracts that share the same sources, +/// even if they differ in bytecode verbatim (e.g. if the contract metadata is different). +/// +/// Identifier depends on the marker of the bytecode of the contract. +/// This might be important, since the metadata can be different for EVM and EraVM, +/// e.g. `zksolc` [supports][zksolc_keccak] keccak256 hash of the metadata as an alternative to CBOR. +/// +/// [zksolc_keccak]: https://matter-labs.github.io/era-compiler-solidity/latest/02-command-line-interface.html#--metadata-hash +// Note: there are missing opportunities here, e.g. Etherscan is able to detect the contracts +// that differ in creation bytecode and/or constructor arguments (for partial match). This is +// less relevant for ZKsync, since there is no concept of creation bytecode there; although +// this may become needed if we will extend the EVM support. +#[derive(Debug, Clone, Copy)] +pub struct ContractIdentifier { + /// Marker of the bytecode of the contract. + pub bytecode_marker: BytecodeMarker, + /// keccak256 hash of the full contract bytecode. + /// Can be used as an identifier of precise contract compilation. + pub bytecode_keccak256: H256, + /// keccak256 hash of the contract bytecode without metadata (e.g. with either + /// CBOR or keccak256 metadata hash being stripped). + /// If no metadata is detected, equal to `bytecode_keccak256`. + pub bytecode_without_metadata_keccak256: H256, + /// Kind of detected metadata. + pub detected_metadata: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Match { + /// Contracts are identical. + Full, + /// Metadata is different. + Partial, + /// No match. + None, +} + +/// Metadata detected in the contract bytecode. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DetectedMetadata { + /// keccak256 metadata (only for EraVM) + Keccak256, + /// CBOR metadata + Cbor, +} + +/// Possible values for the metadata hashes structure. +/// Details can be found here: https://docs.soliditylang.org/en/latest/metadata.html +/// +/// We're not really interested in the values here, we just want to make sure that we +/// can deserialize the metadata. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +struct CborMetadata { + #[serde(skip_serializing_if = "Option::is_none")] + ipfs: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + bzzr1: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + bzzr0: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + experimental: Option, + #[serde(skip_serializing_if = "Option::is_none")] + solc: Option>, +} + +impl ContractIdentifier { + pub fn from_bytecode(bytecode_marker: BytecodeMarker, bytecode: &[u8]) -> Self { + // Calculate the hash for bytecode with metadata. + let bytecode_keccak256 = H256(keccak256(bytecode)); + + // Try to detect metadata. + // CBOR takes precedence (since keccak doesn't have direct markers, so it's partially a + // fallback). + let (detected_metadata, bytecode_without_metadata_keccak256) = + if let Some(hash) = Self::detect_cbor_metadata(bytecode_marker, bytecode) { + (Some(DetectedMetadata::Cbor), hash) + } else if let Some(hash) = Self::detect_keccak_metadata(bytecode_marker, bytecode) { + (Some(DetectedMetadata::Keccak256), hash) + } else { + // Fallback + (None, bytecode_keccak256) + }; + + Self { + bytecode_marker, + bytecode_keccak256, + bytecode_without_metadata_keccak256, + detected_metadata, + } + } + + /// Will try to detect keccak256 metadata hash (only for EraVM) + fn detect_keccak_metadata(bytecode_marker: BytecodeMarker, bytecode: &[u8]) -> Option { + // For EraVM, the one option for metadata hash is keccak256 hash of the metadata. + if bytecode_marker == BytecodeMarker::EraVm { + // For metadata, we might have padding: it takes either 32 or 64 bytes depending + // on whether the amount of words in the contract is odd, so we need to check + // if there is padding. + let bytecode_without_metadata = Self::strip_padding(bytecode, 32)?; + let hash = H256(keccak256(bytecode_without_metadata)); + Some(hash) + } else { + None + } + } + + /// Will try to detect CBOR metadata. + fn detect_cbor_metadata(bytecode_marker: BytecodeMarker, bytecode: &[u8]) -> Option { + let length = bytecode.len(); + + // Last two bytes is the length of the metadata in big endian. + if length < 2 { + return None; + } + let metadata_length = + u16::from_be_bytes([bytecode[length - 2], bytecode[length - 1]]) as usize; + // Including size + let full_metadata_length = metadata_length + 2; + + // Get slice for the metadata. + if length < full_metadata_length { + return None; + } + let raw_metadata = &bytecode[length - full_metadata_length..length - 2]; + // Try decoding. We are not interested in the actual value. + let _metadata: CborMetadata = match ciborium::from_reader(raw_metadata) { + Ok(metadata) => metadata, + Err(_) => return None, + }; + + // Strip metadata and calculate hash. + let bytecode_without_metadata = match bytecode_marker { + BytecodeMarker::Evm => { + // On EVM, there is no padding. + &bytecode[..length - full_metadata_length] + } + BytecodeMarker::EraVm => { + // On EraVM, there is padding: + // 1. We must align the metadata length to 32 bytes. + // 2. We may need to add 32 bytes of padding. + let aligned_metadata_length = metadata_length.div_ceil(32) * 32; + Self::strip_padding(bytecode, aligned_metadata_length)? + } + }; + let hash = H256(keccak256(bytecode_without_metadata)); + Some(hash) + } + + /// Adds one word to the metadata length and check if it's a padding word. + /// If it is, strips the padding. + /// Returns `None` if `metadata_length` + padding won't fit into the bytecode. + fn strip_padding(bytecode: &[u8], metadata_length: usize) -> Option<&[u8]> { + const PADDING_WORD: [u8; 32] = [0u8; 32]; + + let length = bytecode.len(); + let metadata_with_padding_length = metadata_length + 32; + if length < metadata_with_padding_length { + return None; + } + if bytecode[length - metadata_with_padding_length..length - metadata_length] == PADDING_WORD + { + // Padding was added, strip it. + Some(&bytecode[..length - metadata_with_padding_length]) + } else { + // Padding wasn't added, strip metadata only. + Some(&bytecode[..length - metadata_length]) + } + } + + /// Checks the kind of match between identifier and other bytecode. + pub fn matches(&self, other: &[u8]) -> Match { + let other_identifier = Self::from_bytecode(self.bytecode_marker, other); + + if self.bytecode_keccak256 == other_identifier.bytecode_keccak256 { + return Match::Full; + } + + // Check if metadata is different. + // Note that here we do not handle "complex" cases, e.g. lack of metadata in one contract + // and presence in another, or different kinds of metadata. This is OK: partial + // match is needed mostly when you cannot reproduce the original metadata, but one always + // can submit the contract with the same metadata kind. + if self.bytecode_without_metadata_keccak256 + == other_identifier.bytecode_without_metadata_keccak256 + { + return Match::Partial; + } + + Match::None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn eravm_cbor_without_padding() { + // Sample contract with no methods, compiled from the root of monorepo with: + // ./etc/zksolc-bin/v1.5.8/zksolc --solc ./etc/solc-bin/zkVM-0.8.28-1.0.1/solc --metadata-hash ipfs --codegen yul test.sol --bin + // (Use `zkstack contract-verifier init` to download compilers) + let data = hex::decode("0000008003000039000000400030043f0000000100200190000000110000c13d0000000900100198000000190000613d000000000101043b0000000a011001970000000b0010009c000000190000c13d0000000001000416000000000001004b000000190000c13d000000000100041a000000800010043f0000000c010000410000001c0001042e0000000001000416000000000001004b000000190000c13d00000020010000390000010000100443000001200000044300000008010000410000001c0001042e00000000010000190000001d000104300000001b000004320000001c0001042e0000001d0001043000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000000000000000000000000000fffffffc000000000000000000000000ffffffff000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000008000000000000000000000000000000000000000000000000000000000a16469706673582212208acf048570dcc1c3ff41bf8f20376049a42ae8a471f2b2ae8c14d8b356d86d79002a").unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + let full_metadata_len = 64; // (CBOR metadata + len bytes) + let bytecode_without_metadata_keccak256 = + H256(keccak256(&data[..data.len() - full_metadata_len])); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, + Some(DetectedMetadata::Cbor), + "Incorrect detected metadata" + ); + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_without_metadata_keccak256, + "Incorrect bytecode without metadata hash" + ); + } + + #[test] + fn eravm_cbor_with_padding() { + // Same as `eravm_cbor_without_padding` but now bytecode has padding. + let data = hex::decode("00000001002001900000000c0000613d0000008001000039000000400010043f0000000001000416000000000001004b0000000c0000c13d00000020010000390000010000100443000001200000044300000005010000410000000f0001042e000000000100001900000010000104300000000e000004320000000f0001042e0000001000010430000000000000000000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a1646970667358221220d5be4da510b089bb58fa6c65f0a387eef966bcf48671a24fb2b1bc7190842978002a").unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + let full_metadata_len = 64 + 32; // (CBOR metadata + len bytes + padding) + let bytecode_without_metadata_keccak256 = + H256(keccak256(&data[..data.len() - full_metadata_len])); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, + Some(DetectedMetadata::Cbor), + "Incorrect detected metadata" + ); + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_without_metadata_keccak256, + "Incorrect bytecode without metadata hash" + ); + } + + #[test] + fn eravm_keccak_without_padding() { + // Sample contract with no methods, compiled from the root of monorepo with: + // ./etc/zksolc-bin/v1.5.8/zksolc --solc ./etc/solc-bin/zkVM-0.8.28-1.0.1/solc --metadata-hash keccak256 --codegen yul test.sol --bin + // (Use `zkstack contract-verifier init` to download compilers) + let data = hex::decode("00000001002001900000000c0000613d0000008001000039000000400010043f0000000001000416000000000001004b0000000c0000c13d00000020010000390000010000100443000001200000044300000005010000410000000f0001042e000000000100001900000010000104300000000e000004320000000f0001042e000000100001043000000000000000000000000000000000000000000000000000000002000000000000000000000000000000400000010000000000000000000a00e4a5f19bb139176aa501024c7032404c065bc0012897fefd9ebc7e9a7677").unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + let full_metadata_len = 32; // (keccak only) + let bytecode_without_metadata_keccak256 = + H256(keccak256(&data[..data.len() - full_metadata_len])); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, + Some(DetectedMetadata::Keccak256), + "Incorrect detected metadata" + ); + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_without_metadata_keccak256, + "Incorrect bytecode without metadata hash" + ); + } + + #[test] + fn eravm_keccak_with_padding() { + // Same as `eravm_keccak_without_padding`, but now bytecode has padding. + let data = hex::decode("0000008003000039000000400030043f0000000100200190000000110000c13d0000000900100198000000190000613d000000000101043b0000000a011001970000000b0010009c000000190000c13d0000000001000416000000000001004b000000190000c13d000000000100041a000000800010043f0000000c010000410000001c0001042e0000000001000416000000000001004b000000190000c13d00000020010000390000010000100443000001200000044300000008010000410000001c0001042e00000000010000190000001d000104300000001b000004320000001c0001042e0000001d0001043000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000000000000000000000000000fffffffc000000000000000000000000ffffffff000000000000000000000000000000000000000000000000000000006d4ce63c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009b1f0a6172ae84051eca37db231c0fa6249349f4ddaf86a87474a587c19d946d").unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + let full_metadata_len = 64; // (keccak + padding) + let bytecode_without_metadata_keccak256 = + H256(keccak256(&data[..data.len() - full_metadata_len])); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, + Some(DetectedMetadata::Keccak256), + "Incorrect detected metadata" + ); + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_without_metadata_keccak256, + "Incorrect bytecode without metadata hash" + ); + } + + #[test] + fn eravm_too_short_bytecode() { + // Random short bytecode + let data = hex::decode("0000008003000039000000400030043f0000000100200190000000110000c13d") + .unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::EraVm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, None, + "Incorrect detected metadata" + ); + // When no metadata is detected, `bytecode_without_metadata_keccak256` is equal to + // `bytecode_keccak256`. + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_keccak256, + "Incorrect bytecode without metadata hash" + ); + } + + #[test] + fn evm_none() { + // Sample contract with no methods, compiled from the root of monorepo with: + // ./etc/solc-bin/0.8.28/solc test.sol --bin --no-cbor-metadata + // (Use `zkstack contract-verifier init` to download compilers) + let data = hex::decode("6080604052348015600e575f5ffd5b50607980601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c80636d4ce63c14602a575b5f5ffd5b60306044565b604051603b91906062565b60405180910390f35b5f5f54905090565b5f819050919050565b605c81604c565b82525050565b5f60208201905060735f8301846055565b9291505056").unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::Evm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, None, + "Incorrect detected metadata" + ); + // When no metadata is detected, `bytecode_without_metadata_keccak256` is equal to + // `bytecode_keccak256`. + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_keccak256, + "Incorrect bytecode without metadata hash" + ); + } + + #[test] + fn evm_cbor() { + // ./etc/solc-bin/0.8.28/solc test.sol --bin --metadata-hash ipfs + let ipfs_bytecode = "6080604052348015600e575f5ffd5b5060af80601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c80636d4ce63c14602a575b5f5ffd5b60306044565b604051603b91906062565b60405180910390f35b5f5f54905090565b5f819050919050565b605c81604c565b82525050565b5f60208201905060735f8301846055565b9291505056fea2646970667358221220bca846db362b62d2eb9891565b12433410e0f6a634657d2c7d1e7469447e8ab564736f6c634300081c0033"; + // ./etc/solc-bin/0.8.28/solc test.sol --bin --metadata-hash none + // Note that cbor will still be included but will only have solc version. + let none_bytecode = "6080604052348015600e575f5ffd5b50608680601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c80636d4ce63c14602a575b5f5ffd5b60306044565b604051603b91906062565b60405180910390f35b5f5f54905090565b5f819050919050565b605c81604c565b82525050565b5f60208201905060735f8301846055565b9291505056fea164736f6c634300081c000a"; + // ./etc/solc-bin/0.8.28/solc test.sol --bin --metadata-hash swarm + let swarm_bytecode = "6080604052348015600e575f5ffd5b5060ae80601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c80636d4ce63c14602a575b5f5ffd5b60306044565b604051603b91906062565b60405180910390f35b5f5f54905090565b5f819050919050565b605c81604c565b82525050565b5f60208201905060735f8301846055565b9291505056fea265627a7a72315820c0def30c57166e97d6a58290213f3b0d1f83532e7a0371c8e2b6dba826bae46164736f6c634300081c0032"; + + // Different variations of the same contract, compiled with different metadata options. + // Tuples of (label, bytecode, size of metadata (including length)). + // Size of metadata can be found using https://playground.sourcify.dev/ + let test_vector = [ + ("ipfs", ipfs_bytecode, 51usize + 2), + ("none", none_bytecode, 10 + 2), + ("swarm", swarm_bytecode, 50 + 2), + ]; + + for (label, bytecode, full_metadata_len) in test_vector { + let data = hex::decode(bytecode).unwrap(); + let bytecode_keccak256 = H256(keccak256(&data)); + let bytecode_without_metadata_keccak256 = + H256(keccak256(&data[..data.len() - full_metadata_len])); + + let identifier = ContractIdentifier::from_bytecode(BytecodeMarker::Evm, &data); + assert_eq!( + identifier.bytecode_keccak256, bytecode_keccak256, + "{label}: Incorrect bytecode hash" + ); + assert_eq!( + identifier.detected_metadata, + Some(DetectedMetadata::Cbor), + "{label}: Incorrect detected metadata" + ); + assert_eq!( + identifier.bytecode_without_metadata_keccak256, bytecode_without_metadata_keccak256, + "{label}: Incorrect bytecode without metadata hash" + ); + } + } +} diff --git a/core/lib/types/src/contract_verification/mod.rs b/core/lib/types/src/contract_verification/mod.rs new file mode 100644 index 000000000000..7f8686941edb --- /dev/null +++ b/core/lib/types/src/contract_verification/mod.rs @@ -0,0 +1,2 @@ +pub mod api; +pub mod contract_identifier; diff --git a/core/lib/types/src/lib.rs b/core/lib/types/src/lib.rs index 8ec98ec0571e..606de9b9e798 100644 --- a/core/lib/types/src/lib.rs +++ b/core/lib/types/src/lib.rs @@ -32,7 +32,7 @@ pub mod aggregated_operations; pub mod blob; pub mod block; pub mod commitment; -pub mod contract_verification_api; +pub mod contract_verification; pub mod debug_flat_call; pub mod fee; pub mod fee_model; diff --git a/core/node/contract_verification_server/src/api_impl.rs b/core/node/contract_verification_server/src/api_impl.rs index b0336fd284b6..3e21c2f2cba8 100644 --- a/core/node/contract_verification_server/src/api_impl.rs +++ b/core/node/contract_verification_server/src/api_impl.rs @@ -7,11 +7,15 @@ use axum::{ response::{IntoResponse, Response}, Json, }; -use zksync_dal::{CoreDal, DalError}; +use zksync_dal::{contract_verification_dal::ContractVerificationDal, CoreDal, DalError}; use zksync_types::{ - bytecode::BytecodeMarker, - contract_verification_api::{ - CompilerVersions, VerificationIncomingRequest, VerificationInfo, VerificationRequestStatus, + bytecode::{trim_bytecode, BytecodeHash, BytecodeMarker}, + contract_verification::{ + api::{ + CompilerVersions, VerificationIncomingRequest, VerificationInfo, VerificationProblem, + VerificationRequestStatus, + }, + contract_identifier::ContractIdentifier, }, Address, }; @@ -220,15 +224,73 @@ impl RestApi { address: Path
, ) -> ApiResult { let method_latency = METRICS.call[&"contract_verification_info"].start(); - let info = self_ + let mut conn = self_ .replica_connection_pool .connection_tagged("api") - .await? - .contract_verification_dal() - .get_contract_verification_info(*address) - .await? - .ok_or(ApiError::VerificationInfoNotFound)?; + .await?; + let mut dal = conn.contract_verification_dal(); + + let info = if let Some(info) = dal.get_contract_verification_info(*address).await? { + info + } else if let Some(partial_match) = + get_partial_match_verification_info(&mut dal, *address).await? + { + partial_match + } else { + return Err(ApiError::VerificationInfoNotFound); + }; method_latency.observe(); Ok(Json(info)) } } + +/// Tries to do a lookup for partial match verification info. +/// Should be called only if a perfect match is not found. +async fn get_partial_match_verification_info( + dal: &mut ContractVerificationDal<'_, '_>, + address: Address, +) -> anyhow::Result> { + let Some(deployed_contract) = dal.get_contract_info_for_verification(address).await? else { + return Ok(None); + }; + + let bytecode_hash = + BytecodeHash::try_from(deployed_contract.bytecode_hash).context("Invalid bytecode hash")?; + let deployed_bytecode = trim_bytecode(bytecode_hash, &deployed_contract.bytecode) + .context("Invalid deployed bytecode")?; + + let identifier = ContractIdentifier::from_bytecode(bytecode_hash.marker(), deployed_bytecode); + let Some((mut info, fetched_keccak256, fetched_keccak256_without_metadata)) = dal + .get_partial_match_verification_info( + identifier.bytecode_keccak256, + identifier.bytecode_without_metadata_keccak256, + ) + .await? + else { + return Ok(None); + }; + + if identifier.bytecode_keccak256 != fetched_keccak256 { + // Sanity check + let has_metadata = identifier.detected_metadata.is_some(); + let hashes_without_metadata_match = + identifier.bytecode_without_metadata_keccak256 == fetched_keccak256_without_metadata; + + if !has_metadata || !hashes_without_metadata_match { + tracing::error!( + contract_address = ?address, + identifier = ?identifier, + fetched_keccak256 = ?fetched_keccak256, + fetched_keccak256_without_metadata = ?fetched_keccak256_without_metadata, + info = ?info, + "Bogus verification info fetched for contract", + ); + anyhow::bail!("Internal error: bogus verification info detected"); + } + + // Mark the contract as partial match (regardless of other issues). + info.verification_problems = vec![VerificationProblem::IncorrectMetadata]; + } + + Ok(Some(info)) +} diff --git a/core/node/contract_verification_server/src/cache.rs b/core/node/contract_verification_server/src/cache.rs index c8e367515287..f7ba10c2bf92 100644 --- a/core/node/contract_verification_server/src/cache.rs +++ b/core/node/contract_verification_server/src/cache.rs @@ -5,7 +5,7 @@ use std::{ use tokio::sync::RwLock; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal, DalError}; -use zksync_types::contract_verification_api::CompilerVersions; +use zksync_types::contract_verification::api::CompilerVersions; /// Compiler versions supported by the contract verifier. #[derive(Debug, Clone)] diff --git a/core/node/contract_verification_server/src/tests.rs b/core/node/contract_verification_server/src/tests.rs index 88b14db68733..3eb27056b960 100644 --- a/core/node/contract_verification_server/src/tests.rs +++ b/core/node/contract_verification_server/src/tests.rs @@ -13,7 +13,7 @@ use zksync_dal::{Connection, Core, CoreDal}; use zksync_node_test_utils::create_l2_block; use zksync_types::{ bytecode::{BytecodeHash, BytecodeMarker}, - contract_verification_api::CompilerVersions, + contract_verification::api::CompilerVersions, get_code_key, Address, L2BlockNumber, ProtocolVersion, StorageLog, }; diff --git a/core/tests/ts-integration/hardhat.config.ts b/core/tests/ts-integration/hardhat.config.ts index a96a83ca3ee3..20f3ecd4f4f7 100644 --- a/core/tests/ts-integration/hardhat.config.ts +++ b/core/tests/ts-integration/hardhat.config.ts @@ -4,7 +4,7 @@ import '@matterlabs/hardhat-zksync-vyper'; export default { zksolc: { - version: '1.5.3', + version: '1.5.10', compilerSource: 'binary', settings: { enableEraVMExtensions: true diff --git a/core/tests/ts-integration/scripts/compile-yul.ts b/core/tests/ts-integration/scripts/compile-yul.ts index 876caacdfab3..868f7d10ae6f 100644 --- a/core/tests/ts-integration/scripts/compile-yul.ts +++ b/core/tests/ts-integration/scripts/compile-yul.ts @@ -7,7 +7,7 @@ import { getZksolcUrl, saltFromUrl } from '@matterlabs/hardhat-zksync-solc'; import { getCompilersDir } from 'hardhat/internal/util/global-dir'; import path from 'path'; -const COMPILER_VERSION = '1.5.3'; +const COMPILER_VERSION = '1.5.10'; const IS_COMPILER_PRE_RELEASE = false; async function compilerLocation(): Promise { diff --git a/core/tests/ts-integration/tests/api/contract-verification.test.ts b/core/tests/ts-integration/tests/api/contract-verification.test.ts index 8f8830ce7516..21657bec9950 100644 --- a/core/tests/ts-integration/tests/api/contract-verification.test.ts +++ b/core/tests/ts-integration/tests/api/contract-verification.test.ts @@ -10,7 +10,7 @@ import { NodeMode } from '../../src/types'; // Regular expression to match ISO dates. const DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?/; -const ZKSOLC_VERSION = 'v1.5.3'; +const ZKSOLC_VERSION = 'v1.5.10'; const SOLC_VERSION = '0.8.26'; const ZK_VM_SOLC_VERSION = 'zkVM-0.8.26-1.0.1'; diff --git a/core/tests/ts-integration/tests/api/debug.test.ts b/core/tests/ts-integration/tests/api/debug.test.ts index 2af18c8438b8..8cde65ac2555 100644 --- a/core/tests/ts-integration/tests/api/debug.test.ts +++ b/core/tests/ts-integration/tests/api/debug.test.ts @@ -29,7 +29,7 @@ describe('Debug methods', () => { test('Should not fail for infinity recursion', async () => { const bytecodePath = `${ testMaster.environment().pathToHome - }/core/tests/ts-integration/contracts/zkasm/artifacts/deep_stak.zkasm/zkasm/deep_stak.zkasm.zbin`; + }/core/tests/ts-integration/contracts/zkasm/artifacts/deep_stak.zkasm/deep_stak.zkasm/deep_stak.zkasm.zbin`; const bytecode = fs.readFileSync(bytecodePath, 'utf-8'); const contractFactory = new zksync.ContractFactory([], bytecode, testMaster.mainAccount()); diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 30e5bc46af77..bc52f1ab2905 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -803,6 +803,33 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "circuit_definitions" version = "0.150.20" @@ -2370,6 +2397,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "handlebars" version = "3.5.5" @@ -8339,6 +8376,7 @@ dependencies = [ "itertools 0.10.5", "prost 0.12.6", "rand 0.8.5", + "rayon", "serde", "serde_json", "sqlx", @@ -8908,6 +8946,7 @@ dependencies = [ "bigdecimal", "blake2 0.10.6", "chrono", + "ciborium", "derive_more", "hex", "itertools 0.10.5", diff --git a/zkstack_cli/Cargo.lock b/zkstack_cli/Cargo.lock index cf60178cc347..58bd4ce4f1ee 100644 --- a/zkstack_cli/Cargo.lock +++ b/zkstack_cli/Cargo.lock @@ -628,6 +628,33 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -2143,6 +2170,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -7391,6 +7428,7 @@ dependencies = [ "bigdecimal", "blake2", "chrono", + "ciborium", "derive_more", "hex", "itertools 0.10.5", From 0f1a5e0d61fbd7a2755bae6191184039f1c46e03 Mon Sep 17 00:00:00 2001 From: Yury Akudovich Date: Thu, 30 Jan 2025 17:02:21 +0100 Subject: [PATCH 51/52] fix: Allow witness_generator to use Prometheus push gateway in continuous mode (#3555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Allow witness_generator to use Prometheus push gateway in continuous mode. Log which metrics method is used on start. ## Why ❔ To keep receiving metrics in any running mode. ## 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`. --------- Co-authored-by: EmilLuta --- .../crates/bin/witness_generator/src/main.rs | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/prover/crates/bin/witness_generator/src/main.rs b/prover/crates/bin/witness_generator/src/main.rs index 8b28ecf3cdd6..b9006341d03c 100644 --- a/prover/crates/bin/witness_generator/src/main.rs +++ b/prover/crates/bin/witness_generator/src/main.rs @@ -104,7 +104,6 @@ async fn main() -> anyhow::Result<()> { let _observability_guard = observability_config.install()?; let started_at = Instant::now(); - let use_push_gateway = opt.batch_size.is_some(); let prover_config = general_config.prover_config.context("prover config")?; let object_store_config = ProverObjectStoreConfig( @@ -121,16 +120,27 @@ async fn main() -> anyhow::Result<()> { let keystore = Keystore::locate().with_setup_path(Some(prover_config.setup_data_path.clone().into())); - let prometheus_config = general_config.prometheus_config.clone(); + let prometheus_config = general_config + .prometheus_config + .context("missing prometheus config")?; - // If the prometheus listener port is not set in the witness generator config, use the one from the prometheus config. - let prometheus_listener_port = if let Some(port) = config.prometheus_listener_port { - port + let prometheus_exporter_config = if prometheus_config.pushgateway_url.is_some() { + let url = prometheus_config + .gateway_endpoint() + .context("missing prometheus gateway endpoint")?; + tracing::info!("Using Prometheus push gateway: {}", url); + PrometheusExporterConfig::push(url, prometheus_config.push_interval()) } else { - prometheus_config - .clone() - .context("prometheus config")? - .listener_port + let prometheus_listener_port = if let Some(port) = config.prometheus_listener_port { + port + } else { + prometheus_config.listener_port + }; + tracing::info!( + "Using Prometheus pull on port: {}", + prometheus_listener_port + ); + PrometheusExporterConfig::pull(prometheus_listener_port) }; let connection_pool = ConnectionPool::::singleton(database_secrets.prover_url()?) @@ -166,20 +176,7 @@ async fn main() -> anyhow::Result<()> { } }; - let prometheus_config = if use_push_gateway { - let prometheus_config = prometheus_config - .clone() - .context("prometheus config needed when use_push_gateway enabled")?; - PrometheusExporterConfig::push( - prometheus_config - .gateway_endpoint() - .context("gateway_endpoint needed when use_push_gateway enabled")?, - prometheus_config.push_interval(), - ) - } else { - PrometheusExporterConfig::pull(prometheus_listener_port as u16) - }; - let prometheus_task = prometheus_config.run(stop_receiver.clone()); + let prometheus_task = prometheus_exporter_config.run(stop_receiver.clone()); let mut tasks = Vec::new(); tasks.push(tokio::spawn(prometheus_task)); From ddc953d138456b6cbe2a9bb2f4518bc7800ba0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Fran=C3=A7a?= Date: Thu, 30 Jan 2025 23:51:35 +0000 Subject: [PATCH 52/52] build(en): Upgrade node version on EN Docker compose files to latest (#3558) --- .../mainnet-external-node-docker-compose.yml | 4 ++-- .../testnet-external-node-docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/guides/external-node/docker-compose-examples/mainnet-external-node-docker-compose.yml b/docs/src/guides/external-node/docker-compose-examples/mainnet-external-node-docker-compose.yml index 5ee9de187bf0..30e0ca8b91a5 100644 --- a/docs/src/guides/external-node/docker-compose-examples/mainnet-external-node-docker-compose.yml +++ b/docs/src/guides/external-node/docker-compose-examples/mainnet-external-node-docker-compose.yml @@ -52,7 +52,7 @@ services: # Generation of consensus secrets. # The secrets are generated iff the secrets file doesn't already exist. generate-secrets: - image: "matterlabs/external-node:2.0-v25.1.0" + image: "matterlabs/external-node:2.0-v26.2.1" entrypoint: [ "/configs/generate_secrets.sh", @@ -61,7 +61,7 @@ services: volumes: - ./configs:/configs external-node: - image: "matterlabs/external-node:2.0-v25.1.0" + image: "matterlabs/external-node:2.0-v26.2.1" entrypoint: [ "/usr/bin/entrypoint.sh", diff --git a/docs/src/guides/external-node/docker-compose-examples/testnet-external-node-docker-compose.yml b/docs/src/guides/external-node/docker-compose-examples/testnet-external-node-docker-compose.yml index 42c5861b79d8..aaa185df0984 100644 --- a/docs/src/guides/external-node/docker-compose-examples/testnet-external-node-docker-compose.yml +++ b/docs/src/guides/external-node/docker-compose-examples/testnet-external-node-docker-compose.yml @@ -52,7 +52,7 @@ services: # Generation of consensus secrets. # The secrets are generated iff the secrets file doesn't already exist. generate-secrets: - image: "matterlabs/external-node:2.0-v24.16.0" + image: "matterlabs/external-node:2.0-v26.2.1" entrypoint: [ "/configs/generate_secrets.sh", @@ -61,7 +61,7 @@ services: volumes: - ./configs:/configs external-node: - image: "matterlabs/external-node:2.0-v24.16.0" + image: "matterlabs/external-node:2.0-v26.2.1" entrypoint: [ "/usr/bin/entrypoint.sh",