diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..77da16e319d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# GitHub CODEOWNERS file +# This file defines who must review and approve changes to specific files or directories +# For more information, see: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# Require approval from the sequencer-cargo-owners team for workspace dependency changes +Cargo.toml @starkware-libs/sequencer-cargo-owners diff --git a/.github/workflows/blockifier_ci.yml b/.github/workflows/blockifier_ci.yml index 572e0f344f2..24e1c930c96 100644 --- a/.github/workflows/blockifier_ci.yml +++ b/.github/workflows/blockifier_ci.yml @@ -92,3 +92,35 @@ jobs: # tracing is not activated by any workspace crate; test the build. - run: cargo build -p blockifier --features tracing - run: cargo test -p blockifier --features tracing + + benchmarking: + runs-on: namespace-profile-medium-ubuntu-24-04-amd64 + if: ${{ github.event_name == 'pull_request' }} + steps: + # Checkout the base branch to benchmark the old code. + - uses: actions/checkout@v4 + with: + ref: ${{ github.base_ref }} + + - uses: ./.github/actions/bootstrap + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + # Restore cargo artifacts build cache. + - name: Restore Cargo build cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: blockifier-bench + cache-on-failure: true + cache-workspace-crates: true + + # Benchmark the base branch code. + - run: cargo run -p bench_tools -- run --package blockifier --out /tmp/base_results + + # Checkout the current branch to benchmark the new code. + - uses: actions/checkout@v4 + with: + clean: false + + # Benchmark the current branch and compare to the previous run. + # - run: cargo run -p bench_tools -- run-and-compare --package blockifier --out /tmp/new_results --regression-limit 8.0 diff --git a/.github/workflows/committer_ci.yml b/.github/workflows/committer_ci.yml index 3bf3c9c6696..802ed828210 100644 --- a/.github/workflows/committer_ci.yml +++ b/.github/workflows/committer_ci.yml @@ -19,6 +19,8 @@ on: env: RUSTFLAGS: "-D warnings -C link-arg=-fuse-ld=lld" + FULL_COMMITTER_FLOW_TIME_LIMIT_NS: "25000000" + TREE_COMPUTATION_FLOW_TIME_LIMIT_NS: "20000000" # On PR events, cancel existing CI runs on this same PR for this workflow. # Also, create different concurrency groups for different pushed commits, on push events. @@ -66,7 +68,7 @@ jobs: runs-on: namespace-profile-medium-ubuntu-24-04-amd64 if: ${{ github.event_name == 'pull_request' }} steps: - # Checkout the base branch to get the old code. + # Checkout the base branch to benchmark the old code. - uses: actions/checkout@v4 with: ref: ${{ github.base_ref }} @@ -88,79 +90,34 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} - # Download the old benchmark inputs. + # Auth with GCS for bench_tools to download inputs. - id: auth uses: "google-github-actions/auth@v2" with: credentials_json: ${{ secrets.COMMITER_PRODUCTS_EXT_WRITER_JSON }} - uses: "google-github-actions/setup-gcloud@v2" - - run: echo "OLD_BENCH_INPUT_FILES_PREFIX=$(cat ./crates/starknet_committer_and_os_cli/src/committer_cli/tests/flow_test_files_prefix)" >> $GITHUB_ENV - - run: gcloud storage cp -r gs://committer-testing-artifacts/$OLD_BENCH_INPUT_FILES_PREFIX/* ./crates/starknet_committer_and_os_cli/test_inputs - # List the existing benchmarks. - - run: | - cargo bench -p starknet_committer_and_os_cli -- --list | grep ': benchmark$' | sed -e "s/: benchmark$//" > benchmarks_list.txt - - # Benchmark the old code. - - run: cargo bench -p starknet_committer_and_os_cli + # Benchmark the base branch code. + - run: cargo run -p bench_tools -- run --package starknet_committer_and_os_cli --out /tmp/base_results - # Backup the downloaded files to avoid re-downloading them if they didn't change (overwritten by checkout). - - run: mv ./crates/starknet_committer_and_os_cli/test_inputs/tree_flow_inputs.json ./crates/starknet_committer_and_os_cli/test_inputs/tree_flow_inputs.json_bu - - run: mv ./crates/starknet_committer_and_os_cli/test_inputs/committer_flow_inputs.json ./crates/starknet_committer_and_os_cli/test_inputs/committer_flow_inputs.json_bu + # Save the input files from the base branch to use in the second benchmark. + - run: cp -r crates/starknet_committer_and_os_cli/test_inputs /tmp/test_inputs - # Checkout the new code. + # Checkout the current branch to benchmark the new code. - uses: actions/checkout@v4 with: clean: false - - run: echo "NEW_BENCH_INPUT_FILES_PREFIX=$(cat ./crates/starknet_committer_and_os_cli/src/committer_cli/tests/flow_test_files_prefix)" >> $GITHUB_ENV - # If the pip requirements on the old commit are different from the new commit, re-install them. + # Re-install pip requirements in case they changed between branches. - run: pip install -r scripts/requirements.txt - # Input files didn't change. - - if: env.OLD_BENCH_INPUT_FILES_PREFIX == env.NEW_BENCH_INPUT_FILES_PREFIX - run: | - mv ./crates/starknet_committer_and_os_cli/test_inputs/tree_flow_inputs.json_bu ./crates/starknet_committer_and_os_cli/test_inputs/tree_flow_inputs.json - mv ./crates/starknet_committer_and_os_cli/test_inputs/committer_flow_inputs.json_bu ./crates/starknet_committer_and_os_cli/test_inputs/committer_flow_inputs.json - - # Input files did change, download new inputs. - - if: env.OLD_BENCH_INPUT_FILES_PREFIX != env.NEW_BENCH_INPUT_FILES_PREFIX - run: | - gcloud storage cp -r gs://committer-testing-artifacts/$NEW_BENCH_INPUT_FILES_PREFIX/* ./crates/starknet_committer_and_os_cli/test_inputs - - # Benchmark the new code, splitting the benchmarks, and prepare the results for posting a comment. - - run: bash ./crates/starknet_committer_and_os_cli/benches/bench_split_and_prepare_post.sh benchmarks_list.txt bench_new.txt - - - run: echo BENCHES_RESULT=$(cat bench_new.txt) >> $GITHUB_ENV - - # Comment with a link to the workflow (or update existing comment on rerun). - - name: Find Comment - if: github.event_name == 'pull_request' - uses: starkware-libs/find-comment@v3 - id: find-benchmark-comment - with: - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.pull_request.number }} - comment-author: "github-actions[bot]" - body-includes: Benchmark movements - - - name: Create comment - # If the PR number is found and the comment is not found, create a new comment. - if: github.event_name == 'pull_request' - && steps.find-benchmark-comment.outputs.comment-id == '' - uses: starkware-libs/create-or-update-comment@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.pull_request.number }} - body: ${{ env.BENCHES_RESULT }} - - - name: Update comment - # If the PR number is found and the comment exists, update it. - if: github.event_name == 'pull_request' - && steps.find-benchmark-comment.outputs.comment-id != '' - uses: starkware-libs/create-or-update-comment@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - comment-id: ${{ steps.find-benchmark-comment.outputs.comment-id }} - edit-mode: replace - body: ${{ env.BENCHES_RESULT }} + # Benchmark the current branch and compare to the previous run. + # Use the saved inputs and enforce absolute time limits (full_committer_flow: 25ms, tree_computation_flow: 20ms). + - run: | + cargo run -p bench_tools -- run-and-compare \ + --package starknet_committer_and_os_cli \ + --out /tmp/new_results \ + --regression-limit 8.0 \ + --input-dir /tmp/test_inputs \ + --set-absolute-time-ns-limit full_committer_flow ${FULL_COMMITTER_FLOW_TIME_LIMIT_NS} \ + --set-absolute-time-ns-limit tree_computation_flow ${TREE_COMPUTATION_FLOW_TIME_LIMIT_NS} \ No newline at end of file diff --git a/.github/workflows/hybrid_system_test.yaml b/.github/workflows/hybrid_system_test.yaml index 60a9c233bf8..a35619d7a35 100644 --- a/.github/workflows/hybrid_system_test.yaml +++ b/.github/workflows/hybrid_system_test.yaml @@ -18,6 +18,7 @@ env: cluster_name: hybrid-system-test crate_triggers: "apollo_node,apollo_deployments,apollo_integration_tests" path_triggers: ".github/workflows/hybrid_system_test.yaml,scripts/*.py,scripts/system_tests/**/*.py,deployments/sequencer/**" + path_triggers_exclude: "scripts/prod/**/*" pvc_storage_class_name: "premium-rwo" anvil_port: "8545" dockerfile_base_path: ./deployments/images/sequencer @@ -61,7 +62,8 @@ jobs: python ./scripts/check_test_trigger.py --output_file $OUTPUT_FILE \ --commit_id ${{ github.event.pull_request.base.sha }} \ --crate_triggers ${{ env.crate_triggers }} \ - --path_triggers ${{ env.path_triggers }} + --path_triggers ${{ env.path_triggers }} \ + --path_triggers_exclude ${{ env.path_triggers_exclude }} should_run=$(cat "$OUTPUT_FILE") echo "Captured output: $should_run" @@ -297,7 +299,7 @@ jobs: run: chmod +x ./target/debug/sequencer_node_setup ./target/debug/sequencer_simulator - name: Create storage files - run: ./target/debug/sequencer_node_setup --output-base-dir ./output --data-prefix-path /data --n-consolidated 1 --n-distributed 0 + run: ./target/debug/sequencer_node_setup --output-base-dir ./output --data-prefix-path /data --n-consolidated 1 --n-hybrid 0 --n-distributed 0 - name: Export application config dir run: | @@ -362,7 +364,7 @@ jobs: --interval ${{ env.check_interval_sec }} - name: Copy state and restart pod - run: pipenv run python ./scripts/system_tests/copy_state_and_restart.py --deployment_config_path ${{ env.deployment_config_path }} --data-dir "./output/data/node_0/executable_0" + run: pipenv run python ./scripts/system_tests/copy_state_and_restart.py --deployment_config_path ${{ env.deployment_config_path }} --data-dir "./output/data/node_0" - name: Port-forward Anvil pod to localhost:${{ env.anvil_port }} run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e32359d70c..0827de2038b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -129,10 +129,6 @@ jobs: run: echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV - run: pip install -r scripts/requirements.txt - # TODO(Gilad): only one test needs this (base_layer_test.rs), once it migrates to - # anvil, remove. - - run: npm install -g ganache@7.4.3 - - name: "Run tests pull request" if: github.event_name == 'pull_request' run: | @@ -171,10 +167,6 @@ jobs: run: echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV - run: pip install -r scripts/requirements.txt - # TODO(Gilad): only one test needs this (base_layer_test.rs), once it migrates to - # anvil, remove. - - run: npm install -g ganache@7.4.3 - - name: "Run integration tests pull request" if: github.event_name == 'pull_request' run: | diff --git a/.github/workflows/main_nightly.yml b/.github/workflows/main_nightly.yml index 49fa0c43ca4..444e5ce1a8c 100644 --- a/.github/workflows/main_nightly.yml +++ b/.github/workflows/main_nightly.yml @@ -30,7 +30,6 @@ jobs: - uses: ./.github/actions/bootstrap with: github_token: ${{ secrets.GITHUB_TOKEN }} - - run: npm install -g ganache@7.4.3 - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov @@ -115,10 +114,6 @@ jobs: LD_LIBRARY_PATH: ${{ env.Python3_ROOT_DIR }}/bin run: echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV - run: pip install -r scripts/requirements.txt - # TODO(Gilad): only one test needs this (base_layer_test.rs), once it migrates to - # anvil, remove. - - run: npm install -g ganache@7.4.3 - - name: "Run integration tests pull request" run: | scripts/run_tests.py --command integration --is_nightly diff --git a/.github/workflows/main_pr.yml b/.github/workflows/main_pr.yml index b6413e13cfb..574181966f9 100644 --- a/.github/workflows/main_pr.yml +++ b/.github/workflows/main_pr.yml @@ -32,13 +32,6 @@ jobs: - name: Install commitlint run: npm install --global @commitlint/cli @commitlint/config-conventional - - name: Validate PR commits with commitlint - if: github.event_name == 'pull_request' && !(contains(github.event.pull_request.title, 'merge-main') || contains(github.event.pull_request.title, 'merge main')) - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: commitlint --from "$BASE_SHA" --to "$HEAD_SHA" --verbose - - name: Validate PR title with commitlint if: github.event_name != 'merge_group' && github.event_name != 'push' && !(contains(github.event.pull_request.title, 'merge-main') || contains(github.event.pull_request.title, 'merge main')) env: diff --git a/.github/workflows/papyrus_docker-publish.yml b/.github/workflows/papyrus_docker-publish.yml deleted file mode 100644 index 47513718b88..00000000000 --- a/.github/workflows/papyrus_docker-publish.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Papyrus-Docker-Publish - -on: - workflow_dispatch: - push: - branches: - - main - - main-v[0-9].** - tags: - - v[0-9].** - - papyrus-v[0-9].** - paths: - - ".github/workflows/papyrus_docker-publish.yml" - - "crates/papyrus**/**" - - "scripts/dependencies.sh" - - "deployments/images/base/Dockerfile" - - "deployments/images/papyrus/Dockerfile" - - pull_request: - paths: - - ".github/workflows/papyrus_docker-publish.yml" - - "crates/papyrus**/**" - - "scripts/dependencies.sh" - - "deployments/images/base/Dockerfile" - - "deployments/images/papyrus/Dockerfile" - -# On PR events, cancel existing CI runs on this same PR for this workflow. -# Also, create different concurrency groups for different pushed commits, on push events. -concurrency: - group: > - ${{ github.workflow }}- - ${{ github.ref }}- - ${{ github.event_name == 'pull_request' && 'PR' || github.sha }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -env: - REGISTRY: ghcr.io - REPO_NAME: ${{ github.repository }} - RUSTFLAGS: "-D warnings -C link-arg=-fuse-ld=lld" - -jobs: - docker-build-push: - runs-on: namespace-profile-large-ubuntu-24-04-amd64 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Not required but recommended - enables build multi-platform images, export cache, etc - # Also workaround for: https://github.com/docker/build-push-action/issues/461 - # https://github.com/docker/setup-buildx-action - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v2.2.1 - - # Login to a Docker registry except on PR - # https://github.com/docker/login-action - - name: Login to registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@v2.1.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - logout: true - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v4.1.1 - with: - images: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}/papyrus - tags: | - type=semver,pattern={{raw}} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=ref,event=pr - # set `main*` tag for the default / release branches. - type=raw,value={{branch}},enable=${{ github.event_name == 'push' && contains(github.ref, 'main') }} - # set `{branch}-{tag}` tag for tag push events. - type=raw,value={{tag}},enable=${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - # For manual triggers of the workflow: - type=raw,value={{branch}}{{tag}}-{{sha}},enable=${{ github.event_name == 'workflow_dispatch' }} - - # Build and push Docker image with Buildx - # https://github.com/docker/build-push-action - - name: Build and push Docker image - uses: docker/build-push-action@v6.13.0 - with: - context: . - file: deployments/images/papyrus/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - no-cache: true diff --git a/.github/workflows/papyrus_nightly-tests.yml b/.github/workflows/papyrus_nightly-tests.yml index 93c96a99c6d..b29c033f7a8 100644 --- a/.github/workflows/papyrus_nightly-tests.yml +++ b/.github/workflows/papyrus_nightly-tests.yml @@ -59,7 +59,6 @@ jobs: - uses: ./.github/actions/bootstrap with: github_token: ${{ secrets.GITHUB_TOKEN }} - - run: npm install -g ganache@7.4.3 - run: | cargo test -p papyrus_node diff --git a/.github/workflows/sequencer_docker-publish.yml b/.github/workflows/sequencer_docker-publish.yml index 37d3045a66f..09a36295757 100644 --- a/.github/workflows/sequencer_docker-publish.yml +++ b/.github/workflows/sequencer_docker-publish.yml @@ -3,30 +3,14 @@ name: Sequencer-Docker-Publish on: workflow_dispatch: push: - branches: - - main - - main-v*.*.* # e.g. main-v0.14.0 - - v*.*.*-integration # e.g. v0.14.0-integration tags: - "v*.*.*" - "APOLLO-*" - paths: - - ".github/workflows/sequencer_docker-publish.yml" - - "crates/**" - - "scripts/dependencies.sh" - - "scripts/install_build_tools.sh" - - "deployments/images/base/Dockerfile" - - "deployments/images/sequencer/Dockerfile" permissions: contents: read packages: write -# On PR events, cancel existing CI runs on this same PR for this workflow. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.job }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - env: REGISTRY: ghcr.io REPO_NAME: ${{ github.repository }} @@ -43,7 +27,6 @@ jobs: # Login to a Docker registry except on PR # https://github.com/docker/login-action - name: Login to registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' uses: docker/login-action@v2.1.0 with: registry: ${{ env.REGISTRY }} @@ -59,15 +42,12 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}/sequencer tags: | + # Tag-based releases type=raw,enable=${{ startsWith(github.ref, 'refs/tags/') && 'true' || 'false' }},value={{tag}} type=semver,pattern={{raw}} type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=ref,event=pr - # set `dev` tag for the default branch (`main`). - type=raw,value=dev,enable={{is_default_branch}} - # set `dev-{{branch}}-{{sha}}` additional tag for the default branch (`main`). - type=raw,value=dev-{{branch}}{{tag}}-{{sha}},enable={{is_default_branch}} + # Manual workflow dispatch type=raw,value={{branch}}{{tag}}-{{sha}},enable=${{ github.event_name == 'workflow_dispatch' }} # Build and push Docker image with Buildx @@ -77,7 +57,7 @@ jobs: with: context: . file: deployments/images/sequencer/Dockerfile - push: ${{ github.event_name != 'pull_request' }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: BUILD_MODE=release diff --git a/.github/workflows/sequencer_docker-test.yml b/.github/workflows/sequencer_docker-test.yml index b4c2c1f0d67..9e2bb94b88a 100644 --- a/.github/workflows/sequencer_docker-test.yml +++ b/.github/workflows/sequencer_docker-test.yml @@ -14,6 +14,7 @@ on: env: crate_triggers: "apollo_node,apollo_dashboard,apollo_integration_tests" path_triggers: ".github/workflows/sequencer_docker-test.yml,scripts/*.py,scripts/system_tests/**/*.py" + path_triggers_exclude: "scripts/prod/**/*" permissions: contents: read @@ -53,7 +54,8 @@ jobs: python ./scripts/check_test_trigger.py --output_file $OUTPUT_FILE \ --commit_id ${{ github.event.pull_request.base.sha }} \ --crate_triggers ${{ env.crate_triggers }} \ - --path_triggers ${{ env.path_triggers }} + --path_triggers ${{ env.path_triggers }} \ + --path_triggers_exclude ${{ env.path_triggers_exclude }} should_run=$(cat "$OUTPUT_FILE") echo "Captured output: $should_run" diff --git a/.github/workflows/verify-deps.yml b/.github/workflows/verify-deps.yml index 6e21c1e4488..1e166f3a0e7 100644 --- a/.github/workflows/verify-deps.yml +++ b/.github/workflows/verify-deps.yml @@ -16,7 +16,6 @@ jobs: - uses: ./.github/actions/bootstrap with: github_token: ${{ secrets.GITHUB_TOKEN }} - - run: npm install -g ganache@7.4.3 - name: Update Dependencies run: cargo update --verbose - name: Build diff --git a/Cargo.lock b/Cargo.lock index f52704d9e33..bc9564e81ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,9 +93,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4ae82946772d69f868b9ef81fc66acb1b149ef9b4601849bec4bcf5da6552e" +checksum = "ae62e633fa48b4190af5e841eb05179841bb8b713945103291e2c0867037c0d1" dependencies = [ "alloy-consensus", "alloy-contract", @@ -113,13 +113,14 @@ dependencies = [ "alloy-signer-local", "alloy-transport", "alloy-transport-http", + "alloy-trie", ] [[package]] name = "alloy-chains" -version = "0.1.69" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e2652684758b0d9b389d248b209ed9fd9989ef489a550265fe4bb8454fe7eb" +checksum = "0bbb778f50ecb0cebfb5c05580948501927508da7bd628833a8c4bd8545e23e2" dependencies = [ "alloy-primitives", "num_enum", @@ -128,15 +129,16 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbf458101ed6c389e9bb70a34ebc56039868ad10472540614816cdedc8f5265" +checksum = "b9b151e38e42f1586a01369ec52a6934702731d07e8509a7307331b09f6c46dc" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-trie", + "alloy-tx-macros", "auto_impl", "c-kzg", "derive_more 2.0.1", @@ -144,16 +146,18 @@ dependencies = [ "k256", "once_cell", "rand 0.8.5", + "secp256k1", "serde", + "serde_json", "serde_with", "thiserror 2.0.16", ] [[package]] name = "alloy-consensus-any" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc982af629e511292310fe85b433427fd38cb3105147632b574abc997db44c91" +checksum = "6e2d5e8668ef6215efdb7dcca6f22277b4e483a5650e05f5de22b2350971f4b8" dependencies = [ "alloy-consensus", "alloy-eips", @@ -165,9 +169,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0a0c1ddee20ecc14308aae21c2438c994df7b39010c26d70f86e1d8fdb8db0" +checksum = "630288cf4f3a34a8c6bc75c03dce1dbd47833138f65f37d53a1661eafc96b83f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -181,14 +185,15 @@ dependencies = [ "alloy-transport", "futures", "futures-util", + "serde_json", "thiserror 2.0.16", ] [[package]] name = "alloy-core" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8bcce99ad10fe02640cfaec1c6bc809b837c783c1d52906aa5af66e2a196f6" +checksum = "5ca96214615ec8cf3fa2a54b32f486eb49100ca7fe7eb0b8c1137cd316e7250a" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -199,15 +204,14 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb8e762aefd39a397ff485bc86df673465c4ad3ec8819cc60833a8a3ba5cdc87" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", - "const-hex", "itoa", "serde", "serde_json", @@ -216,9 +220,9 @@ dependencies = [ [[package]] name = "alloy-eip2124" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675264c957689f0fd75f5993a73123c2cc3b5c235a38f5b9037fe6c826bfb2c0" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -229,9 +233,9 @@ dependencies = [ [[package]] name = "alloy-eip2930" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -240,9 +244,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b15b13d38b366d01e818fe8e710d4d702ef7499eacd44926a06171dd9585d0c" +checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -252,9 +256,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e86967eb559920e4b9102e4cb825fe30f2e9467988353ce4809f0d3f2c90cd4" +checksum = "e5434834adaf64fa20a6fb90877bc1d33214c41b055cc49f82189c98614368cc" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -266,29 +270,31 @@ dependencies = [ "c-kzg", "derive_more 2.0.1", "either", - "once_cell", "serde", + "serde_with", "sha2", + "thiserror 2.0.16", ] [[package]] name = "alloy-genesis" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40de6f5b53ecf5fd7756072942f41335426d9a3704cd961f77d854739933bcf" +checksum = "919a8471cfbed7bcd8cf1197a57dda583ce0e10c6385f6ff4e8b41304b223392" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-serde", "alloy-trie", "serde", + "serde_with", ] [[package]] name = "alloy-hardforks" -version = "0.1.4" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473ee2ab7f5262b36e8fbc1b5327d5c9d488ab247e31ac739b929dbe2444ae79" +checksum = "3165210652f71dfc094b051602bafd691f506c54050a174b1cba18fb5ef706a3" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -299,9 +305,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6beff64ad0aa6ad1019a3db26fef565aefeb011736150ab73ed3366c3cfd1b" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -311,12 +317,13 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27434beae2514d4a2aa90f53832cbdf6f23e4b5e2656d95eaf15f9276e2418b6" +checksum = "d7c69f6c9c68a1287c9d5ff903d0010726934de0dac10989be37b75a29190d55" dependencies = [ "alloy-primitives", "alloy-sol-types", + "http 1.3.1", "serde", "serde_json", "thiserror 2.0.16", @@ -325,9 +332,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a33a38c7486b1945f8d093ff027add2f3a8f83c7300dbad6165cc49150085e" +checksum = "8eaf2ae05219e73e0979cb2cf55612aafbab191d130f203079805eaf881cca58" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -351,9 +358,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db973a7a23cbe96f2958e5687c51ce2d304b5c6d0dc5ccb3de8667ad8476f50b" +checksum = "e58f4f345cef483eab7374f2b6056973c7419ffe8ad35e994b7a7f5d8e0c7ba4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -364,9 +371,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "846c2248472c3a7efa8d9d6c51af5b545a88335af0ed7a851d01debfc3b03395" +checksum = "61321a0dbc084c2c9f2b07aa34f10db7ac80065c01721e567e5426d882c73de6" dependencies = [ "alloy-genesis", "alloy-hardforks", @@ -385,24 +392,24 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" +checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more 2.0.1", - "foldhash", - "hashbrown 0.15.5", + "foldhash 0.2.0", + "hashbrown 0.16.0", "indexmap 2.11.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.8.5", + "rand 0.9.2", "ruint", "rustc-hash 2.1.1", "serde", @@ -412,9 +419,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b03bde77ad73feae14aa593bcabb932c8098c0f0750ead973331cfc0003a4e1" +checksum = "de2597751539b1cc8fe4204e5325f9a9ed83fcacfb212018dfcfa7877e76de21" dependencies = [ "alloy-chains", "alloy-consensus", @@ -427,6 +434,7 @@ dependencies = [ "alloy-rpc-client", "alloy-rpc-types-anvil", "alloy-rpc-types-eth", + "alloy-signer", "alloy-sol-types", "alloy-transport", "alloy-transport-http", @@ -434,6 +442,7 @@ dependencies = [ "async-trait", "auto_impl", "dashmap", + "either", "futures", "futures-utils-wasm", "lru 0.13.0", @@ -473,15 +482,14 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445a3298c14fae7afb5b9f2f735dead989f3dd83020c2ab8e48ed95d7b6d1acb" +checksum = "edf8eb8be597cfa8c312934d2566ec4516f066d69164f9212d7a148979fdcfd8" dependencies = [ "alloy-json-rpc", "alloy-primitives", "alloy-transport", "alloy-transport-http", - "async-stream", "futures", "pin-project", "reqwest 0.12.23", @@ -491,16 +499,15 @@ dependencies = [ "tokio-stream", "tower 0.5.2", "tracing", - "tracing-futures", "url", "wasmtimer", ] [[package]] name = "alloy-rpc-types" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157deaec6ba2ad7854f16146e4cd60280e76593eed79fdcb06e0fa8b6c60f77" +checksum = "339af7336571dd39ae3a15bde08ae6a647e62f75350bd415832640268af92c06" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -510,9 +517,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a80ee83ef97e7ffd667a81ebdb6154558dfd5e8f20d8249a10a12a1671a04b3" +checksum = "83d98fb386a462e143f5efa64350860af39950c49e7c0cbdba419c16793116ef" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -522,9 +529,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604dea1f00fd646debe8033abe8e767c732868bf8a5ae9df6321909ccbc99c56" +checksum = "fbde0801a32d21c5f111f037bee7e22874836fba7add34ed4a6919932dd7cf23" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -533,9 +540,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e13d71eac04513a71af4b3df580f52f2b4dcbff9d971cc9a52519acf55514cb" +checksum = "361cd87ead4ba7659bda8127902eda92d17fa7ceb18aba1676f7be10f7222487" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -548,14 +555,15 @@ dependencies = [ "itertools 0.14.0", "serde", "serde_json", + "serde_with", "thiserror 2.0.16", ] [[package]] name = "alloy-serde" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1cd73fc054de6353c7f22ff9b846b0f0f145cd0112da07d4119e41e9959207" +checksum = "64600fc6c312b7e0ba76f73a381059af044f4f21f43e07f51f1fa76c868fe302" dependencies = [ "alloy-primitives", "serde", @@ -564,9 +572,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96fbde54bee943cd94ebacc8a62c50b38c7dfd2552dcd79ff61aea778b1bfcc" +checksum = "5772858492b26f780468ae693405f895d6a27dea6e3eab2c36b6217de47c2647" dependencies = [ "alloy-primitives", "async-trait", @@ -579,9 +587,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6e72002cc1801d8b41e9892165e3a6551b7bd382bd9d0414b21e90c0c62551" +checksum = "f4195b803d0a992d8dbaab2ca1986fc86533d4bc80967c0cce7668b26ad99ef9" dependencies = [ "alloy-consensus", "alloy-network", @@ -595,9 +603,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -609,9 +617,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -628,9 +636,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "alloy-json-abi", "const-hex", @@ -646,9 +654,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", "winnow 0.7.13", @@ -656,24 +664,25 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43d5e60466a440230c07761aa67671d4719d46f43be8ea6e7ed334d8db4a9ab" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-macro", - "const-hex", "serde", ] [[package]] name = "alloy-transport" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec325c2af8562ef355c02aeb527c755a07e9d8cf6a1e65dda8d0bf23e29b2c" +checksum = "025a940182bddaeb594c26fe3728525ae262d0806fe6a4befdf5d7bc13d54bce" dependencies = [ "alloy-json-rpc", + "alloy-primitives", + "auto_impl", "base64 0.22.1", "derive_more 2.0.1", "futures", @@ -691,9 +700,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.12.6" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a082c9473c6642cce8b02405a979496126a03b096997888e86229afad05db06c" +checksum = "e3b5064d1e1e1aabc918b5954e7fb8154c39e77ec6903a581b973198b26628fa" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -706,20 +715,33 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" +checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more 1.0.0", + "derive_more 2.0.1", "nybbles", "serde", "smallvec", "tracing", ] +[[package]] +name = "alloy-tx-macros" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e52276fdb553d3c11563afad2898f4085165e4093604afe3d78b69afbf408f" +dependencies = [ + "alloy-primitives", + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -797,9 +819,21 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "apollo_base_layer_tests" +version = "0.16.0-rc.1" +dependencies = [ + "alloy", + "async-trait", + "colored 3.0.0", + "papyrus_base_layer", + "starknet_api", + "url", +] + [[package]] name = "apollo_batcher" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_batcher_config", "apollo_batcher_types", @@ -845,7 +879,7 @@ dependencies = [ [[package]] name = "apollo_batcher_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_storage", @@ -857,7 +891,7 @@ dependencies = [ [[package]] name = "apollo_batcher_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -878,7 +912,7 @@ dependencies = [ [[package]] name = "apollo_central_sync" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_central_sync_config", "apollo_class_manager_types", @@ -913,7 +947,7 @@ dependencies = [ [[package]] name = "apollo_central_sync_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_starknet_client", @@ -925,7 +959,7 @@ dependencies = [ [[package]] name = "apollo_class_manager" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_class_manager_config", "apollo_class_manager_types", @@ -948,7 +982,7 @@ dependencies = [ [[package]] name = "apollo_class_manager_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_storage", @@ -959,7 +993,7 @@ dependencies = [ [[package]] name = "apollo_class_manager_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_compile_to_casm_types", "apollo_infra", @@ -982,7 +1016,7 @@ dependencies = [ [[package]] name = "apollo_compilation_utils" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "assert_matches", @@ -1001,7 +1035,7 @@ dependencies = [ [[package]] name = "apollo_compile_to_casm" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_compilation_utils", "apollo_compile_to_casm_types", @@ -1023,12 +1057,14 @@ dependencies = [ [[package]] name = "apollo_compile_to_casm_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", "apollo_proc_macros", "async-trait", + "blockifier_test_utils", + "expect-test", "mockall", "serde", "serde_json", @@ -1040,7 +1076,7 @@ dependencies = [ [[package]] name = "apollo_compile_to_native" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_compilation_utils", "apollo_compile_to_native_types", @@ -1056,7 +1092,7 @@ dependencies = [ [[package]] name = "apollo_compile_to_native_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1065,7 +1101,7 @@ dependencies = [ [[package]] name = "apollo_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "apollo_test_utils", @@ -1087,7 +1123,7 @@ dependencies = [ [[package]] name = "apollo_config_manager" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_config_manager_config", @@ -1103,11 +1139,12 @@ dependencies = [ "tempfile", "tokio", "tracing", + "tracing-test", ] [[package]] name = "apollo_config_manager_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1116,7 +1153,7 @@ dependencies = [ [[package]] name = "apollo_config_manager_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_consensus_config", "apollo_infra", @@ -1134,8 +1171,9 @@ dependencies = [ [[package]] name = "apollo_consensus" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ + "apollo_config_manager_types", "apollo_consensus_config", "apollo_infra_utils", "apollo_metrics", @@ -1152,6 +1190,7 @@ dependencies = [ "lru 0.12.5", "mockall", "prost", + "rstest", "serde", "starknet-types-core", "starknet_api", @@ -1165,7 +1204,7 @@ dependencies = [ [[package]] name = "apollo_consensus_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_protobuf", @@ -1176,7 +1215,7 @@ dependencies = [ [[package]] name = "apollo_consensus_manager" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_batcher_types", "apollo_class_manager_types", @@ -1205,7 +1244,7 @@ dependencies = [ [[package]] name = "apollo_consensus_manager_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_consensus_config", @@ -1219,7 +1258,7 @@ dependencies = [ [[package]] name = "apollo_consensus_orchestrator" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_batcher", "apollo_batcher_types", @@ -1278,7 +1317,7 @@ dependencies = [ [[package]] name = "apollo_consensus_orchestrator_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1289,7 +1328,7 @@ dependencies = [ [[package]] name = "apollo_dashboard" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_batcher", "apollo_class_manager", @@ -1314,6 +1353,7 @@ dependencies = [ "apollo_storage", "blockifier", "indexmap 2.11.0", + "rstest", "serde", "serde_json", "serde_with", @@ -1323,16 +1363,17 @@ dependencies = [ [[package]] name = "apollo_deployments" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "alloy", "apollo_config", "apollo_http_server_config", + "apollo_infra", "apollo_infra_utils", "apollo_monitoring_endpoint_config", - "apollo_network", "apollo_node", "apollo_node_config", + "apollo_rpc", "indexmap 2.11.0", "libp2p", "serde", @@ -1347,7 +1388,7 @@ dependencies = [ [[package]] name = "apollo_gateway" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_class_manager_types", "apollo_config", @@ -1398,7 +1439,7 @@ dependencies = [ [[package]] name = "apollo_gateway_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_compilation_utils", "apollo_config", @@ -1413,7 +1454,7 @@ dependencies = [ [[package]] name = "apollo_gateway_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_network_types", @@ -1434,7 +1475,7 @@ dependencies = [ [[package]] name = "apollo_http_server" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_gateway_types", "apollo_http_server_config", @@ -1469,7 +1510,7 @@ dependencies = [ [[package]] name = "apollo_http_server_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1478,7 +1519,7 @@ dependencies = [ [[package]] name = "apollo_infra" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_infra_utils", @@ -1508,7 +1549,7 @@ dependencies = [ [[package]] name = "apollo_infra_utils" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_proc_macros", "assert-json-diff", @@ -1520,6 +1561,7 @@ dependencies = [ "serde", "serde_json", "socket2 0.5.10", + "strum 0.25.0", "tempfile", "thiserror 1.0.69", "tokio", @@ -1530,10 +1572,11 @@ dependencies = [ [[package]] name = "apollo_integration_tests" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "alloy", "anyhow", + "apollo_base_layer_tests", "apollo_batcher", "apollo_batcher_config", "apollo_class_manager", @@ -1551,14 +1594,18 @@ dependencies = [ "apollo_http_server_config", "apollo_infra", "apollo_infra_utils", + "apollo_l1_endpoint_monitor", "apollo_l1_endpoint_monitor_config", "apollo_l1_gas_price", "apollo_l1_gas_price_provider_config", "apollo_l1_gas_price_types", + "apollo_l1_provider", "apollo_l1_provider_config", + "apollo_l1_provider_types", "apollo_l1_scraper_config", "apollo_mempool_config", "apollo_mempool_p2p_config", + "apollo_metrics", "apollo_monitoring_endpoint", "apollo_monitoring_endpoint_config", "apollo_network", @@ -1583,6 +1630,7 @@ dependencies = [ "mempool_test_utils", "metrics 0.24.2", "metrics-exporter-prometheus", + "mockall", "papyrus_base_layer", "pretty_assertions", "rstest", @@ -1600,7 +1648,7 @@ dependencies = [ [[package]] name = "apollo_l1_endpoint_monitor" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "alloy", "apollo_infra", @@ -1617,7 +1665,7 @@ dependencies = [ [[package]] name = "apollo_l1_endpoint_monitor_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1627,7 +1675,7 @@ dependencies = [ [[package]] name = "apollo_l1_endpoint_monitor_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -1643,7 +1691,7 @@ dependencies = [ [[package]] name = "apollo_l1_gas_price" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_infra", @@ -1670,7 +1718,7 @@ dependencies = [ [[package]] name = "apollo_l1_gas_price_provider_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1681,7 +1729,7 @@ dependencies = [ [[package]] name = "apollo_l1_gas_price_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -1700,9 +1748,10 @@ dependencies = [ [[package]] name = "apollo_l1_provider" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "alloy", + "apollo_base_layer_tests", "apollo_batcher_types", "apollo_infra", "apollo_infra_utils", @@ -1726,12 +1775,11 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tracing", - "url", ] [[package]] name = "apollo_l1_provider_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1741,7 +1789,7 @@ dependencies = [ [[package]] name = "apollo_l1_provider_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -1760,7 +1808,7 @@ dependencies = [ [[package]] name = "apollo_l1_scraper_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1770,7 +1818,7 @@ dependencies = [ [[package]] name = "apollo_mempool" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config_manager_types", "apollo_infra", @@ -1784,6 +1832,9 @@ dependencies = [ "apollo_time", "assert_matches", "async-trait", + "blockifier", + "blockifier_test_utils", + "criterion", "derive_more 0.99.20", "indexmap 2.11.0", "itertools 0.12.1", @@ -1804,7 +1855,7 @@ dependencies = [ [[package]] name = "apollo_mempool_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1813,7 +1864,7 @@ dependencies = [ [[package]] name = "apollo_mempool_p2p" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_class_manager_types", "apollo_gateway_types", @@ -1837,7 +1888,7 @@ dependencies = [ [[package]] name = "apollo_mempool_p2p_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_network", @@ -1847,7 +1898,7 @@ dependencies = [ [[package]] name = "apollo_mempool_p2p_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -1864,7 +1915,7 @@ dependencies = [ [[package]] name = "apollo_mempool_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -1882,7 +1933,7 @@ dependencies = [ [[package]] name = "apollo_metrics" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "indexmap 2.11.0", "metrics 0.24.2", @@ -1897,7 +1948,7 @@ dependencies = [ [[package]] name = "apollo_monitoring_endpoint" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "apollo_infra", @@ -1924,7 +1975,7 @@ dependencies = [ [[package]] name = "apollo_monitoring_endpoint_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -1933,7 +1984,7 @@ dependencies = [ [[package]] name = "apollo_network" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_metrics", @@ -1974,7 +2025,7 @@ dependencies = [ [[package]] name = "apollo_network_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_test_utils", "lazy_static", @@ -1985,7 +2036,7 @@ dependencies = [ [[package]] name = "apollo_node" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "apollo_batcher", @@ -2031,7 +2082,7 @@ dependencies = [ [[package]] name = "apollo_node_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_batcher_config", "apollo_class_manager_config", @@ -2067,7 +2118,7 @@ dependencies = [ [[package]] name = "apollo_p2p_sync" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_class_manager_types", "apollo_network", @@ -2102,7 +2153,7 @@ dependencies = [ [[package]] name = "apollo_p2p_sync_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -2111,7 +2162,7 @@ dependencies = [ [[package]] name = "apollo_proc_macros" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "lazy_static", "proc-macro2", @@ -2121,7 +2172,7 @@ dependencies = [ [[package]] name = "apollo_proc_macros_tests" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_metrics", "apollo_proc_macros", @@ -2135,10 +2186,11 @@ dependencies = [ [[package]] name = "apollo_protobuf" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_test_utils", "bytes", + "derive_more 0.99.20", "indexmap 2.11.0", "lazy_static", "papyrus_common", @@ -2159,9 +2211,10 @@ dependencies = [ [[package]] name = "apollo_reverts" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", + "apollo_metrics", "apollo_storage", "futures", "serde", @@ -2172,7 +2225,7 @@ dependencies = [ [[package]] name = "apollo_rpc" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "apollo_class_manager_types", @@ -2225,7 +2278,7 @@ dependencies = [ [[package]] name = "apollo_rpc_execution" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "apollo_class_manager_types", @@ -2257,7 +2310,7 @@ dependencies = [ [[package]] name = "apollo_sierra_compilation_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "serde", @@ -2266,7 +2319,7 @@ dependencies = [ [[package]] name = "apollo_signature_manager" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -2288,7 +2341,7 @@ dependencies = [ [[package]] name = "apollo_signature_manager_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -2305,7 +2358,7 @@ dependencies = [ [[package]] name = "apollo_staking" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_consensus", "apollo_state_sync_types", @@ -2323,7 +2376,7 @@ dependencies = [ [[package]] name = "apollo_starknet_client" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_test_utils", @@ -2357,7 +2410,7 @@ dependencies = [ [[package]] name = "apollo_starknet_os_program" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "blockifier", @@ -2372,7 +2425,7 @@ dependencies = [ [[package]] name = "apollo_state_reader" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_class_manager_types", "apollo_storage", @@ -2389,7 +2442,7 @@ dependencies = [ [[package]] name = "apollo_state_sync" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_central_sync", "apollo_class_manager_types", @@ -2409,6 +2462,7 @@ dependencies = [ "futures", "indexmap 2.11.0", "libp2p", + "mockall", "papyrus_common", "rand_chacha 0.3.1", "starknet-types-core", @@ -2419,7 +2473,7 @@ dependencies = [ [[package]] name = "apollo_state_sync_config" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_central_sync_config", "apollo_config", @@ -2434,7 +2488,7 @@ dependencies = [ [[package]] name = "apollo_state_sync_metrics" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -2447,7 +2501,7 @@ dependencies = [ [[package]] name = "apollo_state_sync_types" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra", "apollo_metrics", @@ -2463,12 +2517,11 @@ dependencies = [ "strum 0.25.0", "strum_macros 0.25.3", "thiserror 1.0.69", - "tokio", ] [[package]] name = "apollo_storage" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_metrics", @@ -2481,6 +2534,7 @@ dependencies = [ "cairo-lang-utils", "camelpaste", "clap", + "futures", "human_bytes", "indexmap 2.11.0", "insta", @@ -2490,6 +2544,7 @@ dependencies = [ "memmap2", "metrics 0.24.2", "metrics-exporter-prometheus", + "nix 0.20.2", "num-bigint 0.4.6", "num-traits", "page_size", @@ -2521,7 +2576,7 @@ dependencies = [ [[package]] name = "apollo_task_executor" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "futures", "rstest", @@ -2531,7 +2586,7 @@ dependencies = [ [[package]] name = "apollo_test_utils" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "cairo-lang-casm", "cairo-lang-starknet-classes", @@ -2552,7 +2607,7 @@ dependencies = [ [[package]] name = "apollo_time" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "chrono", "mockall", @@ -3317,6 +3372,20 @@ dependencies = [ "serde", ] +[[package]] +name = "bench_tools" +version = "0.16.0-rc.1" +dependencies = [ + "apollo_infra_utils", + "clap", + "criterion", + "glob", + "rstest", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "bincode" version = "2.0.1" @@ -3448,6 +3517,22 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -3501,7 +3586,7 @@ dependencies = [ [[package]] name = "blockifier" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "apollo_compilation_utils", @@ -3560,7 +3645,7 @@ dependencies = [ [[package]] name = "blockifier_reexecution" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_gateway", "apollo_gateway_config", @@ -3587,7 +3672,7 @@ dependencies = [ [[package]] name = "blockifier_test_utils" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "cairo-lang-starknet-classes", @@ -3701,9 +3786,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "1.0.3" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" dependencies = [ "blst", "cc", @@ -4248,34 +4333,6 @@ dependencies = [ "xshell", ] -[[package]] -name = "cairo-lang-test-plugin" -version = "2.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e90cf75528c423cd6b6faaab2dde0c1b23efe36103e1e57f338293552ee16f" -dependencies = [ - "anyhow", - "cairo-lang-compiler", - "cairo-lang-debug", - "cairo-lang-defs", - "cairo-lang-filesystem", - "cairo-lang-lowering", - "cairo-lang-parser", - "cairo-lang-semantic", - "cairo-lang-sierra", - "cairo-lang-sierra-generator", - "cairo-lang-starknet", - "cairo-lang-starknet-classes", - "cairo-lang-syntax", - "cairo-lang-utils", - "indoc 2.0.6", - "itertools 0.14.0", - "num-bigint 0.4.6", - "num-traits", - "serde", - "starknet-types-core", -] - [[package]] name = "cairo-lang-test-utils" version = "2.12.3" @@ -4308,33 +4365,23 @@ dependencies = [ [[package]] name = "cairo-native" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a1b73479b3b3676bf81d2e3586f7e7d234227d7bdb6d8635b0256af7a41592" +checksum = "4353361398d63962a78dfeada310701dcff26218ee0aee8754354dc81a5b38a0" dependencies = [ - "anyhow", "aquamarine", "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-secp256k1 0.5.0", "ark-secp256r1 0.5.0", "bumpalo", - "cairo-lang-compiler", - "cairo-lang-defs", - "cairo-lang-filesystem", "cairo-lang-runner", - "cairo-lang-semantic", "cairo-lang-sierra", "cairo-lang-sierra-ap-change", "cairo-lang-sierra-gas", "cairo-lang-sierra-to-casm", - "cairo-lang-starknet", "cairo-lang-starknet-classes", - "cairo-lang-test-plugin", "cairo-lang-utils", - "cc", - "clap", - "colored 2.2.0", "educe 0.5.11", "itertools 0.14.0", "keccak", @@ -4353,11 +4400,9 @@ dependencies = [ "sha2", "starknet-curve", "starknet-types-core", - "stats_alloc", "tempfile", "thiserror 2.0.16", "tracing", - "tracing-subscriber", "utf8_iter", ] @@ -5108,6 +5153,16 @@ dependencies = [ "darling_macro 0.20.11", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + [[package]] name = "darling_core" version = "0.14.4" @@ -5136,6 +5191,21 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim 0.11.1", + "syn 2.0.106", +] + [[package]] name = "darling_macro" version = "0.14.4" @@ -5158,6 +5228,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.106", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -6120,18 +6201,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - [[package]] name = "fixed-hash" version = "0.8.0" @@ -6190,6 +6259,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -6798,7 +6873,17 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", "serde", ] @@ -6857,6 +6942,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_fmt" version = "0.3.0" @@ -7089,6 +7183,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.2", "tower-service", + "webpki-roots 1.0.2", ] [[package]] @@ -7991,9 +8086,9 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce8f59622ed408c318c9b5eca17f1a1154159e3738b5c4d5a22a0dd3700c906" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" dependencies = [ "lambdaworks-math", "rand 0.8.5", @@ -8005,11 +8100,13 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "405d65a26831650ba348a503a2881ed7a0483ef3ec17f66e0fc8e2f9c97fc7ca" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" dependencies = [ "getrandom 0.2.16", + "num-bigint 0.4.6", + "num-traits", "rand 0.8.5", "serde", "serde_json", @@ -8497,7 +8594,6 @@ checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.3", "libc", - "redox_syscall 0.5.17", ] [[package]] @@ -8727,7 +8823,7 @@ dependencies = [ [[package]] name = "mempool_test_utils" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "assert_matches", @@ -9048,7 +9144,7 @@ dependencies = [ [[package]] name = "native_blockifier" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_compile_to_native_types", "apollo_state_reader", @@ -9452,13 +9548,14 @@ dependencies = [ [[package]] name = "nybbles" -version = "0.3.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +checksum = "bfa11e84403164a9f12982ab728f3c67c6fd4ab5b5f0254ffc217bdbd3b28ab0" dependencies = [ "alloy-rlp", - "const-hex", + "cfg-if", "proptest", + "ruint", "serde", "smallvec", ] @@ -9614,16 +9711,13 @@ dependencies = [ [[package]] name = "papyrus_base_layer" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "alloy", "apollo_config", "apollo_l1_endpoint_monitor_types", "assert_matches", "async-trait", - "colored 3.0.0", - "ethers", - "ethers-core", "futures", "hex", "mockall", @@ -9631,8 +9725,6 @@ dependencies = [ "serde", "starknet-types-core", "starknet_api", - "tar", - "tempfile", "thiserror 1.0.69", "tokio", "tracing", @@ -9642,7 +9734,7 @@ dependencies = [ [[package]] name = "papyrus_common" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_test_utils", "assert_matches", @@ -9658,7 +9750,7 @@ dependencies = [ [[package]] name = "papyrus_load_test" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "assert_matches", @@ -9676,7 +9768,7 @@ dependencies = [ [[package]] name = "papyrus_monitoring_gateway" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_config", "apollo_starknet_client", @@ -9699,7 +9791,7 @@ dependencies = [ [[package]] name = "papyrus_node" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "anyhow", "apollo_central_sync", @@ -10708,6 +10800,7 @@ checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", + "serde", ] [[package]] @@ -10771,6 +10864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", + "serde", ] [[package]] @@ -11106,6 +11200,8 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.31", "rustls-pki-types", "serde", "serde_json", @@ -11113,6 +11209,7 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.2", "tokio-util", "tower 0.5.2", "tower-http", @@ -11122,6 +11219,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots 1.0.2", ] [[package]] @@ -11766,6 +11864,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -11843,18 +11962,28 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -12054,7 +12183,7 @@ dependencies = [ [[package]] name = "shared_execution_objects" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "blockifier", "rstest", @@ -12156,7 +12285,7 @@ dependencies = [ [[package]] name = "sizeof" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "size-of", "sizeof_internal", @@ -12166,14 +12295,14 @@ dependencies = [ [[package]] name = "sizeof_internal" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "starknet-types-core", ] [[package]] name = "sizeof_macro" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "proc-macro2", "quote", @@ -12340,7 +12469,7 @@ dependencies = [ "base64 0.21.7", "crypto-bigint", "flate2", - "foldhash", + "foldhash 0.1.5", "hex", "indexmap 2.11.0", "num-traits", @@ -12395,9 +12524,9 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c043ab183b1cb1daab10d592719d27f283e7ef7e7ac8accd243b4e70b4e1c0d8" +checksum = "ab92594a86ac627dd4c8d3350362cc8035e55c548c27c71dfa4c9fc6b3b6ab1a" dependencies = [ "blake2", "digest 0.10.7", @@ -12415,7 +12544,7 @@ dependencies = [ [[package]] name = "starknet_api" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "assert_matches", @@ -12451,7 +12580,7 @@ dependencies = [ [[package]] name = "starknet_committer" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "hex", "pretty_assertions", @@ -12468,7 +12597,7 @@ dependencies = [ [[package]] name = "starknet_committer_and_os_cli" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_starknet_os_program", "assert_matches", @@ -12504,7 +12633,7 @@ dependencies = [ [[package]] name = "starknet_os" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "apollo_infra_utils", "apollo_starknet_os_program", @@ -12540,6 +12669,7 @@ dependencies = [ "rstest", "serde", "serde_json", + "serde_with", "sha2", "sha3", "shared_execution_objects", @@ -12555,7 +12685,7 @@ dependencies = [ [[package]] name = "starknet_os_flow_tests" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "blockifier", "blockifier_test_utils", @@ -12566,7 +12696,7 @@ dependencies = [ [[package]] name = "starknet_patricia" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "async-recursion", "derive_more 0.99.20", @@ -12588,7 +12718,7 @@ dependencies = [ [[package]] name = "starknet_patricia_storage" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "hex", "serde", @@ -12614,12 +12744,6 @@ dependencies = [ "rand 0.6.5", ] -[[package]] -name = "stats_alloc" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0e04424e733e69714ca1bbb9204c1a57f09f5493439520f9f68c132ad25eec" - [[package]] name = "string_cache" version = "0.8.9" @@ -12759,9 +12883,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.25" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", @@ -12849,17 +12973,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tar" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "target-lexicon" version = "0.12.16" @@ -13363,7 +13476,7 @@ dependencies = [ [[package]] name = "toml_test_utils" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "serde", "toml 0.8.23", @@ -13486,8 +13599,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "futures", - "futures-task", "pin-project", "tracing", ] @@ -14619,7 +14730,7 @@ dependencies = [ [[package]] name = "workspace_tests" -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" dependencies = [ "toml_test_utils", ] @@ -14687,16 +14798,6 @@ dependencies = [ "time", ] -[[package]] -name = "xattr" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" -dependencies = [ - "libc", - "rustix 1.0.8", -] - [[package]] name = "xml-rs" version = "0.8.27" diff --git a/Cargo.toml b/Cargo.toml index b16e9b81fc5..c432c73006f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ + "crates/apollo_base_layer_tests", "crates/apollo_batcher", "crates/apollo_batcher_config", "crates/apollo_batcher_types", @@ -83,6 +84,7 @@ members = [ "crates/apollo_task_executor", "crates/apollo_test_utils", "crates/apollo_time", + "crates/bench_tools", "crates/blockifier", "crates/blockifier_reexecution", "crates/blockifier_test_utils", @@ -109,15 +111,16 @@ members = [ ] [workspace.package] -version = "0.16.0-rc.0" +version = "0.16.0-rc.1" edition = "2021" repository = "https://github.com/starkware-libs/sequencer/" license = "Apache-2.0" license-file = "LICENSE" [workspace.dependencies] -alloy = "0.12" +alloy = "1.0.38" anyhow = "1.0.44" +apollo_base_layer_tests.path = "crates/apollo_base_layer_tests" apollo_batcher.path = "crates/apollo_batcher" apollo_batcher_config.path = "crates/apollo_batcher_config" apollo_batcher_types.path = "crates/apollo_batcher_types" @@ -126,12 +129,12 @@ apollo_central_sync_config.path = "crates/apollo_central_sync_config" apollo_class_manager.path = "crates/apollo_class_manager" apollo_class_manager_config.path = "crates/apollo_class_manager_config" apollo_class_manager_types.path = "crates/apollo_class_manager_types" -apollo_compilation_utils = { path = "crates/apollo_compilation_utils", version = "0.16.0-rc.0" } +apollo_compilation_utils = { path = "crates/apollo_compilation_utils", version = "0.16.0-rc.1" } apollo_compile_to_casm.path = "crates/apollo_compile_to_casm" apollo_compile_to_casm_types.path = "crates/apollo_compile_to_casm_types" -apollo_compile_to_native = { path = "crates/apollo_compile_to_native", version = "0.16.0-rc.0" } -apollo_compile_to_native_types = { path = "crates/apollo_compile_to_native_types", version = "0.16.0-rc.0" } -apollo_config = { path = "crates/apollo_config", version = "0.16.0-rc.0" } +apollo_compile_to_native = { path = "crates/apollo_compile_to_native", version = "0.16.0-rc.1" } +apollo_compile_to_native_types = { path = "crates/apollo_compile_to_native_types", version = "0.16.0-rc.1" } +apollo_config = { path = "crates/apollo_config", version = "0.16.0-rc.1" } apollo_config_manager.path = "crates/apollo_config_manager" apollo_config_manager_config.path = "crates/apollo_config_manager_config" apollo_config_manager_types.path = "crates/apollo_config_manager_types" @@ -149,7 +152,7 @@ apollo_gateway_types.path = "crates/apollo_gateway_types" apollo_http_server.path = "crates/apollo_http_server" apollo_http_server_config.path = "crates/apollo_http_server_config" apollo_infra.path = "crates/apollo_infra" -apollo_infra_utils = { path = "crates/apollo_infra_utils", version = "0.16.0-rc.0" } +apollo_infra_utils = { path = "crates/apollo_infra_utils", version = "0.16.0-rc.1" } apollo_integration_tests.path = "crates/apollo_integration_tests" apollo_l1_endpoint_monitor.path = "crates/apollo_l1_endpoint_monitor" apollo_l1_endpoint_monitor_config.path = "crates/apollo_l1_endpoint_monitor_config" @@ -167,7 +170,7 @@ apollo_mempool_p2p.path = "crates/apollo_mempool_p2p" apollo_mempool_p2p_config.path = "crates/apollo_mempool_p2p_config" apollo_mempool_p2p_types.path = "crates/apollo_mempool_p2p_types" apollo_mempool_types.path = "crates/apollo_mempool_types" -apollo_metrics = { path = "crates/apollo_metrics", version = "0.16.0-rc.0" } +apollo_metrics = { path = "crates/apollo_metrics", version = "0.16.0-rc.1" } apollo_monitoring_endpoint.path = "crates/apollo_monitoring_endpoint" apollo_monitoring_endpoint_config.path = "crates/apollo_monitoring_endpoint_config" apollo_network.path = "crates/apollo_network" @@ -176,7 +179,7 @@ apollo_node.path = "crates/apollo_node" apollo_node_config.path = "crates/apollo_node_config" apollo_p2p_sync.path = "crates/apollo_p2p_sync" apollo_p2p_sync_config.path = "crates/apollo_p2p_sync_config" -apollo_proc_macros = { path = "crates/apollo_proc_macros", version = "0.16.0-rc.0" } +apollo_proc_macros = { path = "crates/apollo_proc_macros", version = "0.16.0-rc.1" } apollo_proc_macros_tests.path = "crates/apollo_proc_macros_tests" apollo_protobuf.path = "crates/apollo_protobuf" apollo_reverts.path = "crates/apollo_reverts" @@ -187,7 +190,7 @@ apollo_signature_manager.path = "crates/apollo_signature_manager" apollo_signature_manager_types.path = "crates/apollo_signature_manager_types" apollo_staking.path = "crates/apollo_staking" apollo_starknet_client.path = "crates/apollo_starknet_client" -apollo_starknet_os_program = { path = "crates/apollo_starknet_os_program", version = "0.16.0-rc.0" } +apollo_starknet_os_program = { path = "crates/apollo_starknet_os_program", version = "0.16.0-rc.1" } apollo_state_reader.path = "crates/apollo_state_reader" apollo_state_sync.path = "crates/apollo_state_sync" apollo_state_sync_config.path = "crates/apollo_state_sync_config" @@ -211,16 +214,17 @@ async-trait = "0.1.79" atomic_refcell = "0.1.13" axum = "0.6.12" base64 = "0.13.0" +bench_tools.path = "crates/bench_tools" bincode = "1.3.3" bisection = "0.1.0" bitvec = "1.0.1" blake2 = "0.10.6" -blockifier = { path = "crates/blockifier", version = "0.16.0-rc.0" } +blockifier = { path = "crates/blockifier", version = "0.16.0-rc.1" } blockifier_reexecution.path = "crates/blockifier_reexecution" -blockifier_test_utils = { path = "crates/blockifier_test_utils", version = "0.16.0-rc.0" } +blockifier_test_utils = { path = "crates/blockifier_test_utils", version = "0.16.0-rc.1" } byteorder = "1.4.3" bytes = "1" -c-kzg = "1.0.3" +c-kzg = "2.1.5" cached = "0.44.0" cairo-felt = "0.9.1" cairo-lang-casm = "2.12.3" @@ -229,7 +233,7 @@ cairo-lang-sierra = "2.12.3" cairo-lang-sierra-to-casm = "2.12.3" cairo-lang-starknet-classes = "2.12.3" cairo-lang-utils = "2.12.3" -cairo-native = "0.6.2" +cairo-native = "0.7.1" cairo-vm = "2.5.0" camelpaste = "0.1.0" chrono = "0.4.26" @@ -246,7 +250,6 @@ enum-as-inner = "0.6.1" enum-assoc = "1.1.0" enum-iterator = "1.4.1" ethers = "2.0.3" -ethers-core = "2.0.3" ethnum = "1.5.0" expect-test = "1.5.1" flate2 = "1.0.24" @@ -338,14 +341,14 @@ sha2 = "0.10.8" sha3 = "0.10.8" shared_execution_objects.path = "crates/shared_execution_objects" simple_logger = "4.0.0" -sizeof = { path = "crates/sizeof", version = "0.16.0-rc.0" } -sizeof_internal = { path = "crates/sizeof/sizeof_internal", version = "0.16.0-rc.0" } -sizeof_macro = { path = "crates/sizeof/sizeof_macro", version = "0.16.0-rc.0" } +sizeof = { path = "crates/sizeof", version = "0.16.0-rc.1" } +sizeof_internal = { path = "crates/sizeof/sizeof_internal", version = "0.16.0-rc.1" } +sizeof_macro = { path = "crates/sizeof/sizeof_macro", version = "0.16.0-rc.1" } socket2 = "0.5.8" starknet-core = "0.16.0" starknet-crypto = "0.8.1" -starknet-types-core = "0.2.1" -starknet_api = { path = "crates/starknet_api", version = "0.16.0-rc.0" } +starknet-types-core = "=0.2.3" +starknet_api = { path = "crates/starknet_api", version = "0.16.0-rc.1" } starknet_committer.path = "crates/starknet_committer" starknet_committer_and_os_cli.path = "crates/starknet_committer_and_os_cli" starknet_os.path = "crates/starknet_os" @@ -357,7 +360,6 @@ statistical = "1.0.0" strum = "0.25.0" strum_macros = "0.25.2" syn = "2.0.39" -tar = "0.4.38" tempfile = "3.7.0" test-case = "3.2.1" test-log = "0.2.14" diff --git a/commitlint.config.js b/commitlint.config.js index c6f62eae38e..1df3e0e161b 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,4 +1,5 @@ -const AllowedScopes = ['apollo_batcher', +const AllowedScopes = ['apollo_base_layer_tests', + 'apollo_batcher', 'apollo_batcher_config', 'apollo_batcher_types', 'apollo_central_sync', @@ -75,6 +76,7 @@ const AllowedScopes = ['apollo_batcher', 'apollo_test_utils', 'apollo_time', 'batcher', + 'bench_tools', 'blockifier', 'blockifier_reexecution', 'blockifier_test_utils', diff --git a/config/papyrus/default_config.json b/config/papyrus/default_config.json index d0d8d93d32e..0ba1b5d03b9 100644 --- a/config/papyrus/default_config.json +++ b/config/papyrus/default_config.json @@ -114,22 +114,22 @@ "privacy": "Public", "value": 5 }, - "consensus.static_config.sync_retry_interval": { + "consensus.dynamic_config.sync_retry_interval": { "description": "The duration (seconds) between sync attempts.", "privacy": "Public", "value": 1.0 }, - "consensus.static_config.timeouts.precommit_timeout": { + "consensus.dynamic_config.timeouts.precommit_timeout": { "description": "The timeout (seconds) for a precommit.", "privacy": "Public", "value": 1.0 }, - "consensus.static_config.timeouts.prevote_timeout": { + "consensus.dynamic_config.timeouts.prevote_timeout": { "description": "The timeout (seconds) for a prevote.", "privacy": "Public", "value": 1.0 }, - "consensus.static_config.timeouts.proposal_timeout": { + "consensus.dynamic_config.timeouts.proposal_timeout": { "description": "The timeout (seconds) for a proposal.", "privacy": "Public", "value": 3.0 @@ -159,11 +159,6 @@ "pointer_target": "chain_id", "privacy": "Public" }, - "context.constant_l2_gas_price": { - "description": "If true, sets STRK gas price to its minimum price from the versioned constants.", - "privacy": "Public", - "value": false - }, "context.l1_da_mode": { "description": "The data availability mode, true: Blob, false: Calldata.", "privacy": "Public", @@ -204,6 +199,46 @@ "privacy": "Public", "value": 1 }, + "context.override_eth_to_fri_rate": { + "description": "Replace the Eth-to-Fri conversion rate with this value.", + "privacy": "Public", + "value": 0 + }, + "context.override_eth_to_fri_rate.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, + "context.override_l1_data_gas_price_wei": { + "description": "Replace the L1 data gas price (wei) with this value.", + "privacy": "Public", + "value": 0 + }, + "context.override_l1_data_gas_price_wei.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, + "context.override_l1_gas_price_wei": { + "description": "Replace the L1 gas price (wei) with this value.", + "privacy": "Public", + "value": 0 + }, + "context.override_l1_gas_price_wei.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, + "context.override_l2_gas_price_fri": { + "description": "Replace the L2 gas price (fri) with this value.", + "privacy": "Public", + "value": 0 + }, + "context.override_l2_gas_price_fri.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, "context.proposal_buffer_size": { "description": "The buffer size for streaming outbound proposals.", "privacy": "Public", @@ -214,6 +249,16 @@ "privacy": "Public", "value": 10000 }, + "context.validator_ids": { + "description": "Optional explicit set of validator IDs (comma separated).", + "privacy": "Public", + "value": "" + }, + "context.validator_ids.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, "monitoring_gateway.collect_metrics": { "description": "If true, collect and return metrics in the monitoring gateway.", "pointer_target": "collect_metrics", @@ -539,4 +584,4 @@ "privacy": "Public", "value": true } -} +} \ No newline at end of file diff --git a/crates/apollo_base_layer_tests/Cargo.toml b/crates/apollo_base_layer_tests/Cargo.toml new file mode 100644 index 00000000000..861bd268e10 --- /dev/null +++ b/crates/apollo_base_layer_tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "apollo_base_layer_tests" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[features] + +[lints] +workspace = true + +[dependencies] +alloy.workspace = true +async-trait.workspace = true +colored.workspace = true +papyrus_base_layer = { workspace = true, features = ["testing"] } +starknet_api.workspace = true +url.workspace = true + +[dev-dependencies] diff --git a/crates/apollo_base_layer_tests/src/anvil_base_layer.rs b/crates/apollo_base_layer_tests/src/anvil_base_layer.rs new file mode 100644 index 00000000000..5df300ac65f --- /dev/null +++ b/crates/apollo_base_layer_tests/src/anvil_base_layer.rs @@ -0,0 +1,310 @@ +use std::ops::RangeInclusive; + +use alloy::node_bindings::NodeError as AnvilError; +use alloy::primitives::{I256, U256}; +use alloy::providers::{DynProvider, Provider, ProviderBuilder}; +use alloy::rpc::types::TransactionReceipt; +use alloy::sol; +use alloy::sol_types::SolValue; +use async_trait::async_trait; +use colored::*; +use papyrus_base_layer::ethereum_base_layer_contract::{ + EthereumBaseLayerConfig, + EthereumBaseLayerContract, + EthereumBaseLayerError, + Starknet, + StarknetL1Contract, +}; +use papyrus_base_layer::{ + BaseLayerContract, + L1BlockHeader, + L1BlockNumber, + L1BlockReference, + L1Event, +}; +use starknet_api::block::BlockHashAndNumber; +use starknet_api::hash::StarkHash; +use starknet_api::transaction::L1HandlerTransaction; +use url::Url; + +/// Initialize an anvil instance under the default port and deploy the Starknet contract. +/// +/// Usage: use this in cargo integration tests (tests under `tests/` dir), which ensure +/// sequential execution of tests and only one instance of Anvil running at once. Using Anvil in +/// unit tests is not supported and is discouraged, since unit tests should not need to run a whole +/// L1 (and they are parallelized, which creates port issues). For unit tests, prefer using +/// `ProviderBuilder::new().on_mocked_client` to mock L1. +#[derive(Debug)] +pub struct AnvilBaseLayer { + pub anvil_provider: DynProvider, + pub ethereum_base_layer: EthereumBaseLayerContract, +} + +impl AnvilBaseLayer { + pub const DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS: StarkHash = + StarkHash::from_hex_unchecked("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + const DEFAULT_ANVIL_PORT: u16 = 8545; + const DEFAULT_ANVIL_L1_DEPLOYED_ADDRESS: &str = "0x5fbdb2315678afecb367f032d93f642f64180aa3"; + + /// Note: if you have port conflicts, you might have a zombie anvil instance + /// running, but that should be impossible if using through this service, you probably have a + /// manually triggered Anvil instance somewhere in your shell. + pub async fn new(block_time: Option) -> Self { + let is_unit_test = cfg!(test); + if is_unit_test { + panic!( + "Don't use Anvil in unit tests, only in integration tests defined in the \ + integration tests crate. For unit tests, mock l1 using `alloy`'s mocked_provider \ + interface." + ); + } + + let anvil_client = ProviderBuilder::new() + .connect_anvil_with_wallet_and_config(|anvil| { + let anvil = anvil.port(Self::DEFAULT_ANVIL_PORT); + if let Some(block_time) = block_time { anvil.block_time(block_time) } else { anvil } + }) + .unwrap_or_else(|error| match error { + AnvilError::SpawnError(e) + if e.to_string().contains("No such file or directory") => + { + panic!( + "\n{}\n{}\n", + "Anvil binary not found!".bold().red(), + "Install instructions (for local development):\n + cargo install \ + --git https://github.com/foundry-rs/foundry anvil --locked --tag=v0.3.0" + .yellow() + ) + } + _ => panic!("Failed to spawn Anvil: {}", error.to_string().red()), + }); + + Starknet::deploy(anvil_client.clone()).await.unwrap(); + + let config = Self::config(); + let url = Self::url(); + let root_client = anvil_client.root().clone(); + let contract = Starknet::new(config.starknet_contract_address, root_client); + + let anvil_base_layer = Self { + anvil_provider: anvil_client.erased(), + ethereum_base_layer: EthereumBaseLayerContract { config, contract, url }, + }; + anvil_base_layer.initialize_mocked_starknet_contract().await; + + anvil_base_layer + } + + pub async fn send_message_to_l2( + &self, + l1_handler: &L1HandlerTransaction, + ) -> TransactionReceipt { + send_message_to_l2(&self.ethereum_base_layer.contract, l1_handler).await + } + + pub fn url() -> Url { + format!("http://127.0.0.1:{}", Self::DEFAULT_ANVIL_PORT).parse().unwrap() + } + + pub fn config() -> EthereumBaseLayerConfig { + EthereumBaseLayerConfig { + starknet_contract_address: Self::DEFAULT_ANVIL_L1_DEPLOYED_ADDRESS.parse().unwrap(), + ..Default::default() + } + } + + pub async fn update_mocked_starknet_contract_state( + &self, + update: MockedStateUpdate, + ) -> Result<(), EthereumBaseLayerError> { + // Size out output in the starknet contract, most of these aren't checked in the mock. + let mut output = vec![U256::from(0); starknet_output::HEADER_SIZE + 2]; + + output[starknet_output::PREV_BLOCK_NUMBER_OFFSET] = U256::from(update.new_block_number - 1); + output[starknet_output::NEW_BLOCK_NUMBER_OFFSET] = U256::from(update.new_block_number); + output[starknet_output::PREV_BLOCK_HASH_OFFSET] = U256::from(update.prev_block_hash); + output[starknet_output::NEW_BLOCK_HASH_OFFSET] = U256::from(update.new_block_hash); + + // Run eth_call first, which simulates the tx and returns errors, unlike eth_send which + // executes without returning errors or output from the contract. + // Note: if this fails and this is the first state update, make sure to set the previous + // block number as 1 and the previous block hash as 0, as documented in the contract + // initializer. + self.ethereum_base_layer + .contract + .updateState(output.clone(), Default::default()) + .call() + .await?; + + self.ethereum_base_layer + .contract + .updateState(output, Default::default()) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + Ok(()) + } + + /// Initialize the mocked Starknet contract with default test values and first block number and + /// hash as 1. + /// + /// Other values are boilerplate to match the offsets in the Starknet solidity contract, a + /// better mock can remove those as well. + /// NOTE: right now this is coupled with the conditionally compiled mocked starknet account at + /// EthereumBaseLayer; It'd be best if it could be included here instead, but this seems + /// nontrivial without duplicating EthereumBaseLayer, which is self-defeating for a test. + async fn initialize_mocked_starknet_contract(&self) { + let init_data = InitializeData { + programHash: U256::from(1), + configHash: U256::from(1), + initialState: StateUpdate { + blockNumber: I256::from_dec_str("1").unwrap(), + ..Default::default() + }, + ..Default::default() + }; + + let encoded_data = init_data.abi_encode(); + let builder = self.ethereum_base_layer.contract.initializeMock(encoded_data.into()); + builder.send().await.unwrap().get_receipt().await.unwrap(); + } +} + +#[async_trait] +impl BaseLayerContract for AnvilBaseLayer { + type Error = EthereumBaseLayerError; + + async fn get_proved_block_at( + &self, + l1_block: L1BlockNumber, + ) -> Result { + self.ethereum_base_layer.get_proved_block_at(l1_block).await + } + + async fn latest_proved_block( + &self, + finality: u64, + ) -> Result, Self::Error> { + self.ethereum_base_layer.latest_proved_block(finality).await + } + + async fn latest_l1_block_number(&self, finality: u64) -> Result { + self.ethereum_base_layer.latest_l1_block_number(finality).await + } + + async fn latest_l1_block( + &self, + finality: u64, + ) -> Result, Self::Error> { + self.ethereum_base_layer.latest_l1_block(finality).await + } + + async fn l1_block_at( + &self, + block_number: L1BlockNumber, + ) -> Result, Self::Error> { + self.ethereum_base_layer.l1_block_at(block_number).await + } + + async fn events<'a>( + &'a self, + block_range: RangeInclusive, + event_identifiers: &'a [&'a str], + ) -> Result, Self::Error> { + self.ethereum_base_layer.events(block_range, event_identifiers).await + } + + async fn get_block_header( + &self, + block_number: L1BlockNumber, + ) -> Result, Self::Error> { + self.ethereum_base_layer.get_block_header(block_number).await + } + + // TODO(Arni): Consider deleting this function from the trait. + async fn get_url(&self) -> Result { + Ok(self.ethereum_base_layer.url.clone()) + } + + async fn set_provider_url(&mut self, _url: Url) -> Result<(), Self::Error> { + unimplemented!("Anvil base layer is tied to a an Anvil server, url is fixed.") + } +} + +/// Converts a given [L1 handler transaction](starknet_api::transaction::L1HandlerTransaction) +/// to match the interface of the given [starknet l1 contract](StarknetL1Contract), and +/// triggers the L1 entry point, which sends the message to L2. +pub async fn send_message_to_l2( + starknet_core_contract: &StarknetL1Contract, + l1_handler: &L1HandlerTransaction, +) -> TransactionReceipt { + const PAID_FEE_ON_L1: U256 = U256::from_be_slice(b"paid"); // Arbitrary value. + + let l2_contract_address = l1_handler.contract_address.0.key().to_hex_string().parse().unwrap(); + let l2_entry_point = l1_handler.entry_point_selector.0.to_hex_string().parse().unwrap(); + + // The calldata of an L1 handler transaction consists of the L1 sender address followed by + // the transaction payload. We remove the sender address to extract the message + // payload. + let payload = + l1_handler.calldata.0[1..].iter().map(|x| x.to_hex_string().parse().unwrap()).collect(); + let msg = starknet_core_contract.sendMessageToL2(l2_contract_address, l2_entry_point, payload); + + msg + // Sets a non-zero fee to be paid on L1. + .value(PAID_FEE_ON_L1) + // Sends the transaction to the Starknet L1 contract. For debugging purposes, replace + // `.send()` with `.call_raw()` to retrieve detailed error messages from L1. + .send().await.expect("Transaction submission to Starknet L1 contract failed.") + // Waits until the transaction is received on L1 and then fetches its receipt. + .get_receipt().await.expect("Transaction was not received on L1 or receipt retrieval failed.") +} + +pub struct MockedStateUpdate { + pub new_block_number: u64, + pub new_block_hash: u64, + // Consider caching and auto-filling this for better UX. + pub prev_block_hash: u64, +} + +// The following structures are used with a mocked version of the Starknet L1 contract. +// This mocked contract (starknet_for_testing.json) differs from the production contract: +// - Includes an `initializeMock` function that bypasses governance requirements. +// - The `updateState` function doesn't require special permissions. +// - Removed a bunch of checks and functionality not necessary and that was difficult to mock,and +// that are not called from the sequencer at this time. +// - Used exclusively for integration testing with Anvil. +sol! { + #[derive(Debug, Default)] + struct StateUpdate { + uint256 globalRoot; + int256 blockNumber; + uint256 blockHash; + } + + #[derive(Debug, Default)] + struct InitializeData { + uint256 programHash; + uint256 aggregatorProgramHash; + address verifier; + uint256 configHash; + StateUpdate initialState; + } +} + +/// Output offsets from the Starknet solidity contract. These correspond to `StarknetOutput`, +/// defined in the public `Output.sol` contract. These are the only offsets for values currently +/// used in the starknet contract mock, if more are needed, which isn't likely, grab the offsets +/// from Output.sol. +mod starknet_output { + pub const PREV_BLOCK_NUMBER_OFFSET: usize = 2; + pub const NEW_BLOCK_NUMBER_OFFSET: usize = 3; + pub const PREV_BLOCK_HASH_OFFSET: usize = 4; + pub const NEW_BLOCK_HASH_OFFSET: usize = 5; + pub const HEADER_SIZE: usize = 10; +} diff --git a/crates/apollo_base_layer_tests/src/lib.rs b/crates/apollo_base_layer_tests/src/lib.rs new file mode 100644 index 00000000000..f36f6f4d5e0 --- /dev/null +++ b/crates/apollo_base_layer_tests/src/lib.rs @@ -0,0 +1 @@ +pub mod anvil_base_layer; diff --git a/crates/apollo_batcher/src/batcher.rs b/crates/apollo_batcher/src/batcher.rs index 0d71cfe8ced..6035e5339a0 100644 --- a/crates/apollo_batcher/src/batcher.rs +++ b/crates/apollo_batcher/src/batcher.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt::Write; use std::sync::Arc; use apollo_batcher_config::config::BatcherConfig; @@ -63,12 +64,16 @@ use crate::metrics::{ register_metrics, ProposalMetricsHandle, BATCHED_TRANSACTIONS, + BATCHER_L1_PROVIDER_ERRORS, LAST_BATCHED_BLOCK_HEIGHT, LAST_PROPOSED_BLOCK_HEIGHT, LAST_SYNCED_BLOCK_HEIGHT, + NUM_TRANSACTION_IN_BLOCK, + PROVING_GAS_IN_LAST_BLOCK, REJECTED_TRANSACTIONS, REVERTED_BLOCKS, REVERTED_TRANSACTIONS, + SIERRA_GAS_IN_LAST_BLOCK, STORAGE_HEIGHT, SYNCED_TRANSACTIONS, }; @@ -78,7 +83,11 @@ use crate::pre_confirmed_block_writer::{ PreconfirmedBlockWriterTrait, }; use crate::pre_confirmed_cende_client::PreconfirmedCendeClientTrait; -use crate::transaction_provider::{ProposeTransactionProvider, ValidateTransactionProvider}; +use crate::transaction_provider::{ + ProposeTransactionProvider, + TxProviderPhase, + ValidateTransactionProvider, +}; use crate::utils::{ deadline_as_instant, proposal_status_from, @@ -127,6 +136,9 @@ pub struct Batcher { /// Each stream is kept until SendProposalContent::Finish/Abort is received, or a new height is /// started. validate_tx_streams: HashMap, + + /// Number of proposals made since coming online. + proposals_counter: u64, } impl Batcher { @@ -156,6 +168,8 @@ impl Batcher { executed_proposals: Arc::new(Mutex::new(HashMap::new())), propose_tx_streams: HashMap::new(), validate_tx_streams: HashMap::new(), + // Allow the first few proposals to be without L1 txs while system starts up. + proposals_counter: 1, } } @@ -220,22 +234,31 @@ impl Batcher { error!("Failed to update gas price in mempool: {}", err); BatcherError::InternalError })?; - self.l1_provider_client + // Ignore errors. If start_block fails, then subsequent calls to l1 provider will fail on + // out of session and l1 provider will restart and bootstrap again. + let _ = self + .l1_provider_client .start_block(SessionState::Propose, propose_block_input.block_info.block_number) .await - .map_err(|err| { + .inspect_err(|err| { error!( "L1 provider is not ready to start proposing block {}: {}. ", propose_block_input.block_info.block_number, err ); - BatcherError::NotReady - })?; + BATCHER_L1_PROVIDER_ERRORS.increment(1); + }); + let start_phase = if self.proposals_counter % self.config.propose_l1_txs_every == 0 { + TxProviderPhase::L1 + } else { + TxProviderPhase::Mempool + }; let tx_provider = ProposeTransactionProvider::new( self.mempool_client.clone(), self.l1_provider_client.clone(), self.config.max_l1_handler_txs_per_block_proposal, propose_block_input.block_info.block_number, + start_phase, ); // A channel to receive the transactions included in the proposed block. @@ -259,6 +282,10 @@ impl Batcher { BlockBuilderExecutionParams { deadline: deadline_as_instant(propose_block_input.deadline)?, is_validator: false, + proposer_idle_detection_delay: self + .config + .block_builder_config + .proposer_idle_detection_delay_millis, }, Box::new(tx_provider), Some(output_tx_sender), @@ -289,6 +316,7 @@ impl Batcher { propose_block_input.proposal_id ); LAST_PROPOSED_BLOCK_HEIGHT.set_lossy(block_number.0); + self.proposals_counter += 1; Ok(()) } @@ -305,16 +333,19 @@ impl Batcher { validate_block_input.retrospective_block_hash, )?; - self.l1_provider_client + // Ignore errors. If start_block fails, then subsequent calls to l1 provider will fail on + // out of session and l1 provider will restart and bootstrap again. + let _ = self + .l1_provider_client .start_block(SessionState::Validate, validate_block_input.block_info.block_number) .await - .map_err(|err| { + .inspect_err(|err| { error!( "L1 provider is not ready to start validating block {}: {}. ", validate_block_input.block_info.block_number, err ); - BatcherError::NotReady - })?; + BATCHER_L1_PROVIDER_ERRORS.increment(1); + }); // A channel to send the transactions to include in the block being validated. let (input_tx_sender, input_tx_receiver) = @@ -338,6 +369,10 @@ impl Batcher { BlockBuilderExecutionParams { deadline: deadline_as_instant(validate_block_input.deadline)?, is_validator: true, + proposer_idle_detection_delay: self + .config + .block_builder_config + .proposer_idle_detection_delay_millis, }, Box::new(tx_provider), None, @@ -610,6 +645,10 @@ impl Batcher { BATCHED_TRANSACTIONS.increment(n_txs); REJECTED_TRANSACTIONS.increment(n_rejected_txs); REVERTED_TRANSACTIONS.increment(n_reverted_count); + NUM_TRANSACTION_IN_BLOCK.record_lossy(n_txs); + SIERRA_GAS_IN_LAST_BLOCK.set_lossy(block_execution_artifacts.bouncer_weights.sierra_gas.0); + PROVING_GAS_IN_LAST_BLOCK + .set_lossy(block_execution_artifacts.bouncer_weights.proving_gas.0); Ok(DecisionReachedResponse { state_diff, @@ -647,6 +686,7 @@ impl Batcher { error!("Failed to commit proposal to storage: {}", err); BatcherError::InternalError })?; + info!("Successfully committed proposal for block {} to storage.", height); // Notify the L1 provider of the new block. let rejected_l1_handler_tx_hashes = rejected_tx_hashes @@ -680,9 +720,7 @@ impl Batcher { ); } } - // Rollback the state diff in the storage. - self.storage_writer.revert_block(height); - return Err(BatcherError::InternalError); + BATCHER_L1_PROVIDER_ERRORS.increment(1); } // Notify the mempool of the new block. @@ -887,25 +925,36 @@ fn log_txs_execution_result( proposal_id: ProposalId, result: &Result>, ) { - // Constructing log message. if let Ok(block_artifacts) = result { - let mut log_msg = format!( + let execution_infos = &block_artifacts.execution_data.execution_infos; + let rejected_hashes = &block_artifacts.execution_data.rejected_tx_hashes; + + // Estimate capacity: base message + (hash + status) per transaction + // TransactionHash is 66 chars (0x + 64 hex), status is ~12 chars, separator is 4 chars + // Total per transaction: ~82 chars + const CHARS_PER_TX: usize = 82; + const BASE_CAPACITY: usize = 80; // Base message length + let total_txs = execution_infos.len() + rejected_hashes.len(); + let estimated_capacity = BASE_CAPACITY + total_txs * CHARS_PER_TX; + + let mut log_msg = String::with_capacity(estimated_capacity); + let _ = write!( + &mut log_msg, "Finished generating proposal {} with {} transactions", proposal_id, - block_artifacts.execution_data.execution_infos.len(), + execution_infos.len(), ); - block_artifacts.execution_data.execution_infos.iter().for_each(|(tx_hash, info)| { - log_msg.push_str(&format!(", {tx_hash}:")); - if info.revert_error.is_some() { - log_msg.push_str(" Reverted"); - } else { - log_msg.push_str(" Successful"); - } - }); - block_artifacts.execution_data.rejected_tx_hashes.iter().for_each(|tx_hash| { - log_msg.push_str(&format!(", {tx_hash}: Rejected")); - }); - info!(log_msg); + + for (tx_hash, info) in execution_infos { + let status = if info.revert_error.is_some() { "Reverted" } else { "Successful" }; + let _ = write!(&mut log_msg, ", {tx_hash}: {status}"); + } + + for tx_hash in rejected_hashes { + let _ = write!(&mut log_msg, ", {tx_hash}: Rejected"); + } + + info!("{}", log_msg); } } diff --git a/crates/apollo_batcher/src/batcher_test.rs b/crates/apollo_batcher/src/batcher_test.rs index 9242d422c89..dd9825f81c0 100644 --- a/crates/apollo_batcher/src/batcher_test.rs +++ b/crates/apollo_batcher/src/batcher_test.rs @@ -72,6 +72,7 @@ use crate::pre_confirmed_block_writer::{ MockPreconfirmedBlockWriterTrait, }; use crate::test_utils::{ + test_l1_handler_txs, test_txs, verify_indexed_execution_infos, FakeProposeBlockBuilder, @@ -213,11 +214,12 @@ fn mock_create_builder_for_propose_block( build_block_result: BlockBuilderResult, ) { block_builder_factory.expect_create_block_builder().times(1).return_once( - move |_, _, _, output_content_sender, _, _, _| { + move |_, _, tx_provider, output_content_sender, _, _, _| { let block_builder = FakeProposeBlockBuilder { output_content_sender: output_content_sender.unwrap(), output_txs, build_block_result: Some(build_block_result), + tx_provider, }; Ok((Box::new(block_builder), abort_signal_sender())) }, @@ -406,8 +408,20 @@ async fn no_active_height() { #[case::proposer(true)] #[case::validator(false)] #[tokio::test] -async fn l1_handler_provider_not_ready(#[case] proposer: bool) { +async fn ignore_l1_handler_provider_not_ready(#[case] proposer: bool) { let mut deps = MockDependencies::default(); + if proposer { + mock_create_builder_for_propose_block( + &mut deps.block_builder_factory, + vec![], + Ok(BlockExecutionArtifacts::create_for_testing()), + ); + } else { + mock_create_builder_for_validate_block( + &mut deps.block_builder_factory, + Ok(BlockExecutionArtifacts::create_for_testing()), + ); + } deps.l1_provider_client.expect_start_block().returning(|_, _| { // The heights are not important for the test. let err = L1ProviderError::UnexpectedHeight { @@ -420,15 +434,9 @@ async fn l1_handler_provider_not_ready(#[case] proposer: bool) { assert_eq!(batcher.start_height(StartHeightInput { height: INITIAL_HEIGHT }).await, Ok(())); if proposer { - assert_eq!( - batcher.propose_block(propose_block_input(PROPOSAL_ID)).await, - Err(BatcherError::NotReady) - ); + batcher.propose_block(propose_block_input(PROPOSAL_ID)).await.unwrap(); } else { - assert_eq!( - batcher.validate_block(validate_block_input(PROPOSAL_ID)).await, - Err(BatcherError::NotReady) - ); + batcher.validate_block(validate_block_input(PROPOSAL_ID)).await.unwrap(); } } @@ -675,6 +683,61 @@ async fn propose_block_full_flow() { assert_proposal_metrics(&metrics, 1, 1, 0, 0); } +#[rstest] +#[tokio::test] +async fn multiple_proposals_with_l1_every_n_proposals() { + const N_PROPOSALS: usize = 4; + const PROPOSALS_L1_MODULATOR: usize = 3; + + // Send a regular tx and an l1 handler tx. + let mut expected_streamed_txs = test_txs(0..1); + expected_streamed_txs.extend(test_l1_handler_txs(1..2)); + let mut block_builder_factory = MockBlockBuilderFactoryTrait::new(); + for _ in 0..N_PROPOSALS { + mock_create_builder_for_propose_block( + &mut block_builder_factory, + expected_streamed_txs.clone(), + Ok(BlockExecutionArtifacts::create_for_testing()), + ); + } + + let mut mock_dependencies = MockDependencies { block_builder_factory, ..Default::default() }; + mock_dependencies.storage_writer.expect_revert_block().times(N_PROPOSALS).returning(|_| ()); + + mock_dependencies + .l1_provider_client + .expect_start_block() + .times(N_PROPOSALS) + .returning(|_, _| Ok(())); + + let mut batcher = create_batcher(mock_dependencies).await; + // Only propose L1 txs every PROPOSALS_L1_MODULATOR proposals. + batcher.config.propose_l1_txs_every = PROPOSALS_L1_MODULATOR.try_into().unwrap(); + + for i in 0..N_PROPOSALS { + batcher.start_height(StartHeightInput { height: INITIAL_HEIGHT }).await.unwrap(); + batcher.propose_block(propose_block_input(PROPOSAL_ID)).await.unwrap(); + let content = batcher + .get_proposal_content(GetProposalContentInput { proposal_id: PROPOSAL_ID }) + .await + .unwrap() + .content; + let txs = assert_matches!(content, GetProposalContent::Txs(txs) => txs); + + if (i + 1) % PROPOSALS_L1_MODULATOR == 0 { + assert_eq!(txs, expected_streamed_txs); + } else { + assert_eq!(txs, test_txs(0..1)); + } + + batcher.await_active_proposal(DUMMY_FINAL_N_EXECUTED_TXS).await.unwrap(); + batcher + .revert_block(RevertBlockInput { height: INITIAL_HEIGHT.prev().unwrap() }) + .await + .unwrap(); + } +} + #[rstest] #[tokio::test] async fn get_height() { @@ -808,15 +871,23 @@ async fn proposal_startup_failure_allows_new_proposals() { Ok(BlockExecutionArtifacts::create_for_testing()), ); let mut l1_provider_client = MockL1ProviderClient::new(); - let error = L1ProviderClientError::L1ProviderError(L1ProviderError::UnexpectedHeight { - expected_height: BlockNumber(1), - got: BlockNumber(0), - }); - l1_provider_client.expect_start_block().once().return_once(|_, _| Err(error)); - l1_provider_client.expect_start_block().once().return_once(|_, _| Ok(())); + l1_provider_client.expect_start_block().returning(|_, _| Ok(())); + let mut mempool_client = MockMempoolClient::new(); + let expected_gas_price = + propose_block_input(PROPOSAL_ID).block_info.gas_prices.strk_gas_prices.l2_gas_price.get(); + let error = MempoolClientError::ClientError(ClientError::CommunicationFailure( + "Mempool not ready".to_string(), + )); + mempool_client + .expect_update_gas_price() + .with(eq(expected_gas_price)) + .return_once(|_| Err(error)); + mempool_client.expect_update_gas_price().with(eq(expected_gas_price)).return_once(|_| Ok(())); + mempool_client.expect_commit_block().with(eq(CommitBlockArgs::default())).returning(|_| Ok(())); let mut batcher = create_batcher(MockDependencies { block_builder_factory, l1_provider_client, + mempool_client, ..Default::default() }) .await; @@ -826,7 +897,7 @@ async fn proposal_startup_failure_allows_new_proposals() { batcher .propose_block(propose_block_input(ProposalId(0))) .await - .expect_err("Expected to fail because of the first L1ProviderClient error"); + .expect_err("Expected to fail because of the first MempoolClient error"); batcher.validate_block(validate_block_input(ProposalId(1))).await.expect("Expected to succeed"); batcher @@ -1152,7 +1223,7 @@ fn validate_batcher_config_failure() { }) )] #[tokio::test] -async fn decision_reached_return_error_when_l1_commit_block_fails( +async fn decision_reached_return_success_when_l1_commit_block_fails( #[case] l1_error: L1ProviderClientError, ) { let mut mock_dependencies = MockDependencies::default(); @@ -1167,7 +1238,7 @@ async fn decision_reached_return_error_when_l1_commit_block_fails( mock_dependencies.storage_writer.expect_commit_proposal().returning(|_, _| Ok(())); - mock_dependencies.storage_writer.expect_revert_block().returning(|_| ()); + mock_dependencies.mempool_client.expect_commit_block().returning(|_| Ok(())); mock_create_builder_for_propose_block( &mut mock_dependencies.block_builder_factory, @@ -1176,5 +1247,5 @@ async fn decision_reached_return_error_when_l1_commit_block_fails( ); let result = batcher_propose_and_commit_block(mock_dependencies).await; - assert!(result.is_err()); + assert!(result.is_ok()); } diff --git a/crates/apollo_batcher/src/block_builder.rs b/crates/apollo_batcher/src/block_builder.rs index fe83320ec80..46364bb15a1 100644 --- a/crates/apollo_batcher/src/block_builder.rs +++ b/crates/apollo_batcher/src/block_builder.rs @@ -1,6 +1,7 @@ use std::cmp::min; use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use std::time::Duration; use apollo_batcher_config::config::BlockBuilderConfig; use apollo_batcher_types::batcher_types::ProposalCommitment; @@ -12,7 +13,7 @@ use apollo_class_manager_types::transaction_converter::{ }; use apollo_class_manager_types::SharedClassManagerClient; use apollo_infra_utils::tracing::LogCompatibleToStringExt; -use apollo_state_reader::papyrus_state::{ClassReader, PapyrusReader}; +use apollo_state_reader::apollo_state::{ApolloReader, ClassReader}; use apollo_storage::StorageReader; use async_trait::async_trait; use blockifier::blockifier::concurrent_transaction_executor::ConcurrentTransactionExecutor; @@ -50,7 +51,12 @@ use tracing::{debug, error, info, trace, warn}; use crate::block_builder::FailOnErrorCause::L1HandlerTransactionValidationFailed; use crate::cende_client_types::{StarknetClientStateDiff, StarknetClientTransactionReceipt}; -use crate::metrics::{PROPOSER_DEFERRED_TXS, VALIDATOR_WASTED_TXS}; +use crate::metrics::{ + record_block_close_reason, + BlockCloseReason, + PROPOSER_DEFERRED_TXS, + VALIDATOR_WASTED_TXS, +}; use crate::pre_confirmed_block_writer::{CandidateTxSender, PreconfirmedTxSender}; use crate::transaction_executor::TransactionExecutorTrait; use crate::transaction_provider::{TransactionProvider, TransactionProviderError}; @@ -85,7 +91,7 @@ pub enum FailOnErrorCause { DeadlineReached, #[error("Transaction failed: {0}")] TransactionFailed(BlockifierTransactionExecutorError), - #[error("L1 Handler transaction validation failed")] + #[error("L1 Handler transaction validation failed: {0}")] L1HandlerTransactionValidationFailed(TransactionProviderError), } @@ -159,6 +165,7 @@ pub trait BlockBuilderTrait: Send { pub struct BlockBuilderExecutionParams { pub deadline: tokio::time::Instant, pub is_validator: bool, + pub proposer_idle_detection_delay: Duration, } pub struct BlockBuilder { @@ -181,6 +188,9 @@ pub struct BlockBuilder { n_concurrent_txs: usize, tx_polling_interval_millis: u64, execution_params: BlockBuilderExecutionParams, + + /// Timestamp when block building started. + block_building_start: tokio::time::Instant, } impl BlockBuilder { @@ -214,6 +224,7 @@ impl BlockBuilder { n_concurrent_txs, tx_polling_interval_millis, execution_params, + block_building_start: tokio::time::Instant::now(), } } } @@ -238,13 +249,19 @@ impl BlockBuilder { if self.execution_params.is_validator { return Err(BlockBuilderError::FailOnError(FailOnErrorCause::DeadlineReached)); } - crate::metrics::BLOCK_CLOSE_REASON.increment( - 1, - &[( - crate::metrics::LABEL_NAME_BLOCK_CLOSE_REASON, - crate::metrics::BlockCloseReason::Deadline.into(), - )], + record_block_close_reason(BlockCloseReason::Deadline); + break; + } + + if self.should_finish_due_to_timeout_while_propose() { + let now = tokio::time::Instant::now(); + let time_since_start = now.duration_since(self.block_building_start); + info!( + "No transactions are being executed and {:?} passed since block building \ + started (timeout is set to {:?}), finishing block building.", + time_since_start, self.execution_params.proposer_idle_detection_delay, ); + record_block_close_reason(BlockCloseReason::IdleExecutionTimeout); break; } if final_n_executed_txs.is_none() { @@ -267,13 +284,7 @@ impl BlockBuilder { // Call `handle_executed_txs()` once more to get the last results. self.handle_executed_txs().await?; info!("Block is full."); - crate::metrics::BLOCK_CLOSE_REASON.increment( - 1, - &[( - crate::metrics::LABEL_NAME_BLOCK_CLOSE_REASON, - crate::metrics::BlockCloseReason::FullBlock.into(), - )], - ); + record_block_close_reason(BlockCloseReason::FullBlock); break; } @@ -313,6 +324,28 @@ impl BlockBuilder { self.n_executed_txs, final_n_executed_txs_nonopt, ); + // Sanity check to avoid panic and skip logging if numbers aren't aligned + if final_n_executed_txs_nonopt <= self.n_executed_txs + && self.n_executed_txs <= self.block_txs.len() + { + debug!( + "Finished building block as {}. Transaction hashes: included in block: {:?}, \ + proposer excluded but we executed: {:?}, not finished executing: {:?}", + if self.execution_params.is_validator { "validator" } else { "proposer" }, + self.block_txs[0..final_n_executed_txs_nonopt] + .iter() + .map(|tx| tx.tx_hash()) + .collect::>(), + self.block_txs[final_n_executed_txs_nonopt..self.n_executed_txs] + .iter() + .map(|tx| tx.tx_hash()) + .collect::>(), + self.block_txs[self.n_executed_txs..] + .iter() + .map(|tx| tx.tx_hash()) + .collect::>(), + ); + } // Move a clone of the executor into the lambda function. let executor = self.executor.clone(); @@ -330,6 +363,7 @@ impl BlockBuilder { casm_hash_computation_data_proving_gas, compiled_class_hashes_for_migration, } = block_summary; + let mut execution_data = std::mem::take(&mut self.execution_data); if let Some(final_n_executed_txs) = final_n_executed_txs { // Remove the transactions that were executed, but eventually not included in the block. @@ -500,6 +534,18 @@ impl BlockBuilder { } } + fn should_finish_due_to_timeout_while_propose(&self) -> bool { + if self.execution_params.is_validator { + return false; + }; + if self.n_txs_in_progress() > 0 { + return false; + }; + let now = tokio::time::Instant::now(); + let time_since_start = now.duration_since(self.block_building_start); + time_since_start >= self.execution_params.proposer_idle_detection_delay + } + async fn sleep(&mut self) { tokio::time::sleep(tokio::time::Duration::from_millis(self.tx_polling_interval_millis)) .await; @@ -622,7 +668,7 @@ pub struct BlockMetadata { // Type definitions for the abort channel required to abort the block builder. pub type AbortSignalSender = tokio::sync::oneshot::Sender<()>; pub type BatcherWorkerPool = - Arc>>>; + Arc>>>; /// The BlockBuilderFactoryTrait is responsible for creating a new block builder. #[cfg_attr(test, automock)] @@ -658,7 +704,7 @@ impl BlockBuilderFactory { block_metadata: BlockMetadata, runtime: tokio::runtime::Handle, ) -> BlockBuilderResult< - ConcurrentTransactionExecutor>, + ConcurrentTransactionExecutor>, > { info!( "preprocess and create transaction executor for block {}", @@ -677,10 +723,10 @@ impl BlockBuilderFactory { ); let class_reader = Some(ClassReader { reader: self.class_manager_client.clone(), runtime }); - let papyrus_reader = - PapyrusReader::new_with_class_reader(self.storage_reader.clone(), height, class_reader); + let apollo_reader = + ApolloReader::new_with_class_reader(self.storage_reader.clone(), height, class_reader); let state_reader = StateReaderAndContractManager { - state_reader: papyrus_reader, + state_reader: apollo_reader, contract_class_manager: self.contract_class_manager.clone(), }; diff --git a/crates/apollo_batcher/src/block_builder_test.rs b/crates/apollo_batcher/src/block_builder_test.rs index 13f573ae004..cc2d1f985e2 100644 --- a/crates/apollo_batcher/src/block_builder_test.rs +++ b/crates/apollo_batcher/src/block_builder_test.rs @@ -1,4 +1,5 @@ use std::sync::Arc; +use std::time::Duration; use apollo_class_manager_types::transaction_converter::TransactionConverter; use apollo_class_manager_types::MockClassManagerClient; @@ -59,6 +60,7 @@ const BLOCK_GENERATION_LONG_DEADLINE_SECS: u64 = 5; const TX_CHANNEL_SIZE: usize = 50; const N_CONCURRENT_TXS: usize = 3; const TX_POLLING_INTERVAL: u64 = 100; +const DEFAULT_IDLE_TIMEOUT_MS: Duration = Duration::from_millis(2000); struct TestExpectations { mock_transaction_executor: MockTransactionExecutorTrait, @@ -66,6 +68,8 @@ struct TestExpectations { expected_block_artifacts: BlockExecutionArtifacts, expected_txs_output: Vec, expected_full_blocks_metric: u64, + expected_idle_execution_timeout_metric: u64, + deadline_secs: u64, } fn output_channel() @@ -128,6 +132,8 @@ fn one_chunk_test_expectations() -> TestExpectations { expected_block_artifacts, expected_txs_output: input_txs, expected_full_blocks_metric: 0, + expected_idle_execution_timeout_metric: 0, + deadline_secs: BLOCK_GENERATION_DEADLINE_SECS, } } @@ -260,6 +266,8 @@ fn two_chunks_test_expectations() -> TestExpectations { expected_block_artifacts, expected_txs_output: chain!(first_chunk.iter(), second_chunk.iter()).cloned().collect(), expected_full_blocks_metric: 0, + expected_idle_execution_timeout_metric: 0, + deadline_secs: BLOCK_GENERATION_DEADLINE_SECS, } } @@ -279,6 +287,8 @@ fn empty_block_test_expectations() -> TestExpectations { expected_block_artifacts, expected_txs_output: vec![], expected_full_blocks_metric: 0, + expected_idle_execution_timeout_metric: 0, + deadline_secs: BLOCK_GENERATION_DEADLINE_SECS, } } @@ -305,6 +315,8 @@ fn block_full_test_expectations(before_is_done: bool) -> TestExpectations { expected_block_artifacts, expected_txs_output: input_txs, expected_full_blocks_metric: 1, + expected_idle_execution_timeout_metric: 0, + deadline_secs: BLOCK_GENERATION_DEADLINE_SECS, } } @@ -357,6 +369,8 @@ fn test_expectations_partial_transaction_execution() -> TestExpectations { expected_block_artifacts, expected_txs_output: input_txs, expected_full_blocks_metric: 0, + expected_idle_execution_timeout_metric: 0, + deadline_secs: BLOCK_GENERATION_DEADLINE_SECS, } } @@ -429,6 +443,52 @@ fn transaction_failed_test_expectations() -> TestExpectations { expected_block_artifacts, expected_txs_output: input_txs, expected_full_blocks_metric: 0, + expected_idle_execution_timeout_metric: 0, + deadline_secs: BLOCK_GENERATION_DEADLINE_SECS, + } +} + +fn idle_execution_timeout_test_expectations() -> TestExpectations { + let input_txs = test_txs(0..2); + let input_txs_clone = input_txs.clone(); + + let mut helper = ExpectationHelper::new(); + helper.expect_successful_get_new_results(0); + helper.expect_is_done(false); + helper.expect_add_txs_to_block(&input_txs); + helper.expect_successful_get_new_results(input_txs.len()); + helper.expect_is_done(false); + // After the timeout check, no more results will be returned. + helper.mock_transaction_executor.expect_get_new_results().returning(Vec::new); + // Keep reporting not done so the timeout can trigger. + helper.mock_transaction_executor.expect_is_done().returning(|| false); + + let expected_block_artifacts = + set_close_block_expectations(&mut helper.mock_transaction_executor, input_txs.len()); + + // Mock provider that returns initial transactions, then empty chunks. + let mut mock_tx_provider = MockTransactionProvider::new(); + + // First call returns the initial transactions. + mock_tx_provider.expect_get_final_n_executed_txs().times(1).return_const(None); + mock_tx_provider + .expect_get_txs() + .times(1) + .with(eq(N_CONCURRENT_TXS)) + .return_once(move |_n_txs| Ok(input_txs)); + + // Subsequent calls return empty (no new transactions). + mock_tx_provider.expect_get_final_n_executed_txs().return_const(None); + mock_tx_provider.expect_get_txs().with(eq(N_CONCURRENT_TXS)).returning(|_n_txs| Ok(vec![])); + + TestExpectations { + mock_transaction_executor: helper.mock_transaction_executor, + mock_tx_provider, + expected_block_artifacts, + expected_txs_output: input_txs_clone, + expected_full_blocks_metric: 0, + expected_idle_execution_timeout_metric: 1, + deadline_secs: 5, } } @@ -591,6 +651,7 @@ async fn verify_build_block_output( result_block_artifacts: BlockExecutionArtifacts, mut output_stream_receiver: UnboundedReceiver, expected_full_blocks_metric: u64, + expected_idle_execution_timeout_metric: u64, metrics: &str, ) { // Verify the transactions in the output channel. @@ -606,6 +667,11 @@ async fn verify_build_block_output( expected_full_blocks_metric, &[(LABEL_NAME_BLOCK_CLOSE_REASON, BlockCloseReason::FullBlock.into())], ); + BLOCK_CLOSE_REASON.assert_eq::( + metrics, + expected_idle_execution_timeout_metric, + &[(LABEL_NAME_BLOCK_CLOSE_REASON, BlockCloseReason::IdleExecutionTimeout.into())], + ); } async fn run_build_block( @@ -615,6 +681,7 @@ async fn run_build_block( is_validator: bool, abort_receiver: tokio::sync::oneshot::Receiver<()>, deadline_secs: u64, + idle_timeout_duration: Duration, ) -> BlockBuilderResult { let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(deadline_secs); let transaction_converter = TransactionConverter::new( @@ -631,7 +698,11 @@ async fn run_build_block( transaction_converter, N_CONCURRENT_TXS, TX_POLLING_INTERVAL, - BlockBuilderExecutionParams { deadline, is_validator }, + BlockBuilderExecutionParams { + deadline, + is_validator, + proposer_idle_detection_delay: idle_timeout_duration, + }, ); block_builder.build_block().await @@ -645,6 +716,7 @@ async fn run_build_block( #[case::block_full_after_is_done(block_full_test_expectations(false))] #[case::deadline_reached_after_first_chunk(test_expectations_partial_transaction_execution())] #[case::transaction_failed(transaction_failed_test_expectations())] +#[case::idle_execution_timeout(idle_execution_timeout_test_expectations())] #[tokio::test] async fn test_build_block(#[case] test_expectations: TestExpectations) { let recorder = PrometheusBuilder::new().build_recorder(); @@ -660,13 +732,20 @@ async fn test_build_block(#[case] test_expectations: TestExpectations) { let (output_tx_sender, output_tx_receiver) = output_channel(); let (_abort_sender, abort_receiver) = tokio::sync::oneshot::channel(); + let idle_timeout_duration = if test_expectations.expected_idle_execution_timeout_metric > 0 { + Duration::from_millis(100) + } else { + DEFAULT_IDLE_TIMEOUT_MS + }; + let result_block_artifacts = run_build_block( test_expectations.mock_transaction_executor, test_expectations.mock_tx_provider, Some(output_tx_sender), false, abort_receiver, - BLOCK_GENERATION_DEADLINE_SECS, + test_expectations.deadline_secs, + idle_timeout_duration, ) .await .unwrap(); @@ -677,6 +756,7 @@ async fn test_build_block(#[case] test_expectations: TestExpectations) { result_block_artifacts, output_tx_receiver, test_expectations.expected_full_blocks_metric, + test_expectations.expected_idle_execution_timeout_metric, &recorder.handle().render(), ) .await; @@ -697,6 +777,7 @@ async fn test_validate_block() { true, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap(); @@ -730,6 +811,7 @@ async fn test_validate_block_excluded_txs() { true, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap(); @@ -761,6 +843,7 @@ async fn test_validate_block_with_error( true, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap_err(); @@ -797,6 +880,7 @@ async fn test_validate_block_l1_handler_validation_error(#[case] status: Invalid true, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await; @@ -846,6 +930,7 @@ async fn test_build_block_abort() { false, abort_receiver, BLOCK_GENERATION_LONG_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await, Err(BlockBuilderError::Aborted) @@ -878,6 +963,7 @@ async fn test_build_block_abort_immediately() { false, abort_receiver, BLOCK_GENERATION_LONG_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await, Err(BlockBuilderError::Aborted) @@ -900,6 +986,7 @@ async fn test_l2_gas_used() { true, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap(); @@ -930,6 +1017,7 @@ async fn test_execution_info_order() { false, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap(); @@ -978,6 +1066,7 @@ async fn failed_l1_handler_transaction_consumed() { false, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap(); @@ -1041,6 +1130,7 @@ async fn partial_chunk_execution_proposer() { is_validator, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await .unwrap(); @@ -1091,6 +1181,7 @@ async fn partial_chunk_execution_validator(#[case] successful: bool) { is_validator, abort_receiver, BLOCK_GENERATION_DEADLINE_SECS, + DEFAULT_IDLE_TIMEOUT_MS, ) .await; diff --git a/crates/apollo_batcher/src/metrics.rs b/crates/apollo_batcher/src/metrics.rs index 2c6fb8b5466..518ef05beef 100644 --- a/crates/apollo_batcher/src/metrics.rs +++ b/crates/apollo_batcher/src/metrics.rs @@ -42,10 +42,15 @@ define_metrics!( MetricCounter { REJECTED_TRANSACTIONS, "batcher_rejected_transactions", "Counter of rejected transactions", init = 0 }, MetricCounter { REVERTED_TRANSACTIONS, "batcher_reverted_transactions", "Counter of reverted transactions across all forks", init = 0 }, MetricCounter { SYNCED_TRANSACTIONS, "batcher_synced_transactions", "Counter of synced transactions", init = 0 }, + MetricHistogram { NUM_TRANSACTION_IN_BLOCK, "batcher_num_transaction_in_block", "Number of transactions in a block"}, + MetricCounter { BATCHER_L1_PROVIDER_ERRORS, "batcher_l1_provider_errors", "Counter of L1 provider errors", init = 0 }, MetricCounter { PRECONFIRMED_BLOCK_WRITTEN, "batcher_preconfirmed_block_written", "Counter of preconfirmed blocks written to storage", init = 0 }, // Block close reason LabeledMetricCounter { BLOCK_CLOSE_REASON, "batcher_block_close_reason", "Number of blocks closed by reason", init = 0 , labels = BLOCK_CLOSE_REASON_LABELS}, + // Block weights + MetricGauge { SIERRA_GAS_IN_LAST_BLOCK, "batcher_sierra_gas_in_last_block", "The sierra gas in the last block"}, + MetricGauge { PROVING_GAS_IN_LAST_BLOCK, "batcher_proving_gas_in_last_block", "The proving gas in the last block"}, }, ); @@ -56,6 +61,9 @@ pub const LABEL_NAME_BLOCK_CLOSE_REASON: &str = "block_close_reason"; pub enum BlockCloseReason { FullBlock, Deadline, + /// Block building finished because no new transactions are being executed and the configured + /// timeout passed. + IdleExecutionTimeout, } generate_permutation_labels! { @@ -63,6 +71,10 @@ generate_permutation_labels! { (LABEL_NAME_BLOCK_CLOSE_REASON, BlockCloseReason), } +pub(crate) fn record_block_close_reason(reason: BlockCloseReason) { + BLOCK_CLOSE_REASON.increment(1, &[(LABEL_NAME_BLOCK_CLOSE_REASON, reason.into())]); +} + pub fn register_metrics(storage_height: BlockNumber) { STORAGE_HEIGHT.register(); STORAGE_HEIGHT.set_lossy(storage_height.0); @@ -83,8 +95,13 @@ pub fn register_metrics(storage_height: BlockNumber) { REVERTED_TRANSACTIONS.register(); SYNCED_TRANSACTIONS.register(); + BATCHER_L1_PROVIDER_ERRORS.register(); PRECONFIRMED_BLOCK_WRITTEN.register(); BLOCK_CLOSE_REASON.register(); + NUM_TRANSACTION_IN_BLOCK.register(); + + SIERRA_GAS_IN_LAST_BLOCK.register(); + PROVING_GAS_IN_LAST_BLOCK.register(); // Blockifier's metrics CALLS_RUNNING_NATIVE.register(); diff --git a/crates/apollo_batcher/src/test_utils.rs b/crates/apollo_batcher/src/test_utils.rs index 11cfd12f574..ba58a8e4be1 100644 --- a/crates/apollo_batcher/src/test_utils.rs +++ b/crates/apollo_batcher/src/test_utils.rs @@ -21,7 +21,7 @@ use crate::block_builder::{ BlockExecutionArtifacts, BlockTransactionExecutionData, }; -use crate::transaction_provider::TransactionProvider; +use crate::transaction_provider::{TransactionProvider, TxProviderPhase}; pub const EXECUTION_INFO_LEN: usize = 10; pub const DUMMY_FINAL_N_EXECUTED_TXS: usize = 12; @@ -58,12 +58,19 @@ pub(crate) struct FakeProposeBlockBuilder { pub output_content_sender: UnboundedSender, pub output_txs: Vec, pub build_block_result: Option>, + pub tx_provider: Box, } #[async_trait] impl BlockBuilderTrait for FakeProposeBlockBuilder { async fn build_block(&mut self) -> BlockBuilderResult { for tx in &self.output_txs { + // Skip L1 txs if the tx_provider was set to mempool phase. + if matches!(tx, InternalConsensusTransaction::L1Handler(_)) + && self.tx_provider.phase() == TxProviderPhase::Mempool + { + continue; + } self.output_content_sender.send(tx.clone()).unwrap(); } diff --git a/crates/apollo_batcher/src/transaction_provider.rs b/crates/apollo_batcher/src/transaction_provider.rs index fb34f8324cf..728c49e91b1 100644 --- a/crates/apollo_batcher/src/transaction_provider.rs +++ b/crates/apollo_batcher/src/transaction_provider.rs @@ -16,6 +16,8 @@ use starknet_api::consensus_transaction::InternalConsensusTransaction; use starknet_api::transaction::TransactionHash; use thiserror::Error; +use crate::metrics::BATCHER_L1_PROVIDER_ERRORS; + type TransactionProviderResult = Result; #[derive(Clone, Debug, Error)] @@ -46,6 +48,10 @@ pub trait TransactionProvider: Send { /// Once `Some()` is returned for the first time, future calls to this method may return `None`. /// Returns `None` in propose mode ([ProposeTransactionProvider]). async fn get_final_n_executed_txs(&mut self) -> Option; + + // TODO(guyn): remove this after refactoring the batcher tests. + #[cfg(test)] + fn phase(&self) -> TxProviderPhase; } #[derive(Clone)] @@ -59,8 +65,9 @@ pub struct ProposeTransactionProvider { } // Keeps track of whether we need to fetch L1 handler transactions or mempool transactions. +// TODO(guyn): make the phase pub(crate) after refactoring the batcher tests. #[derive(Clone, Debug, PartialEq)] -enum TxProviderPhase { +pub enum TxProviderPhase { L1, Mempool, } @@ -71,13 +78,14 @@ impl ProposeTransactionProvider { l1_provider_client: SharedL1ProviderClient, max_l1_handler_txs_per_block: usize, height: BlockNumber, + start_phase: TxProviderPhase, ) -> Self { Self { mempool_client, l1_provider_client, max_l1_handler_txs_per_block, height, - phase: TxProviderPhase::L1, + phase: start_phase, n_l1handler_txs_so_far: 0, } } @@ -89,7 +97,11 @@ impl ProposeTransactionProvider { Ok(self .l1_provider_client .get_txs(n_txs, self.height) - .await? + .await + .inspect_err(|_err| { + BATCHER_L1_PROVIDER_ERRORS.increment(1); + }) + .unwrap_or_default() .into_iter() .map(InternalConsensusTransaction::L1Handler) .collect()) @@ -142,6 +154,12 @@ impl TransactionProvider for ProposeTransactionProvider { async fn get_final_n_executed_txs(&mut self) -> Option { None } + + // TODO(guyn): remove this after refactoring the batcher tests. + #[cfg(test)] + fn phase(&self) -> TxProviderPhase { + self.phase.clone() + } } pub struct ValidateTransactionProvider { @@ -177,8 +195,16 @@ impl TransactionProvider for ValidateTransactionProvider { for tx in &buffer { if let InternalConsensusTransaction::L1Handler(tx) = tx { - let l1_validation_status = - self.l1_provider_client.validate(tx.tx_hash, self.height).await?; + let l1_validation_status = self + .l1_provider_client + .validate(tx.tx_hash, self.height) + .await + .inspect_err(|_err| { + BATCHER_L1_PROVIDER_ERRORS.increment(1); + }) + .unwrap_or(L1ValidationStatus::Invalid( + L1InvalidValidationStatus::L1ProviderError, + )); if let L1ValidationStatus::Invalid(validation_status) = l1_validation_status { return Err(TransactionProviderError::L1HandlerTransactionValidationFailed { tx_hash: tx.tx_hash, @@ -194,4 +220,10 @@ impl TransactionProvider for ValidateTransactionProvider { // Return None if the receiver is empty or closed unexpectedly. self.final_n_executed_txs_receiver.try_recv().ok() } + + // TODO(guyn): remove this after refactoring the batcher tests. + #[cfg(test)] + fn phase(&self) -> TxProviderPhase { + panic!("Phase is only relevant to proposing transactions.") + } } diff --git a/crates/apollo_batcher/src/transaction_provider_test.rs b/crates/apollo_batcher/src/transaction_provider_test.rs index d2842cd9171..7d824339098 100644 --- a/crates/apollo_batcher/src/transaction_provider_test.rs +++ b/crates/apollo_batcher/src/transaction_provider_test.rs @@ -19,6 +19,7 @@ use crate::transaction_provider::{ ProposeTransactionProvider, TransactionProvider, TransactionProviderError, + TxProviderPhase, ValidateTransactionProvider, }; @@ -63,15 +64,24 @@ impl MockDependencies { } } - fn propose_tx_provider(self) -> ProposeTransactionProvider { + fn propose_tx_provider_with_phase(self, phase: TxProviderPhase) -> ProposeTransactionProvider { ProposeTransactionProvider::new( Arc::new(self.mempool_client), Arc::new(self.l1_provider_client), MAX_L1_HANDLER_TXS_PER_BLOCK, HEIGHT, + phase, ) } + fn propose_tx_provider(self) -> ProposeTransactionProvider { + self.propose_tx_provider_with_phase(TxProviderPhase::L1) + } + + fn propose_tx_provider_mempool_phase(self) -> ProposeTransactionProvider { + self.propose_tx_provider_with_phase(TxProviderPhase::Mempool) + } + fn validate_tx_provider(self) -> ValidateTransactionProvider { self.validate_tx_provider_with_final_n_executed_txs().0 } @@ -203,6 +213,16 @@ async fn no_more_l1_handler(mut mock_dependencies: MockDependencies) { assert!(data.iter().all(|tx| matches!(tx, InternalConsensusTransaction::RpcTransaction(_)))); } +#[rstest] +#[tokio::test] +async fn provider_in_mempool_phase_no_l1(mut mock_dependencies: MockDependencies) { + mock_dependencies.expect_get_mempool_txs(MAX_TXS_PER_FETCH); + let mut tx_provider = mock_dependencies.propose_tx_provider_mempool_phase(); + let txs = tx_provider.get_txs(MAX_TXS_PER_FETCH).await.unwrap(); + let data = assert_matches!(txs, txs if txs.len() == MAX_TXS_PER_FETCH => txs); + assert!(data.iter().all(|tx| matches!(tx, InternalConsensusTransaction::RpcTransaction(_)))); +} + #[rstest] #[tokio::test] async fn validate_flow(mut mock_dependencies: MockDependencies) { diff --git a/crates/apollo_batcher_config/src/config.rs b/crates/apollo_batcher_config/src/config.rs index 81e30d57906..836156c903c 100644 --- a/crates/apollo_batcher_config/src/config.rs +++ b/crates/apollo_batcher_config/src/config.rs @@ -1,5 +1,7 @@ use std::collections::BTreeMap; +use std::time::Duration; +use apollo_config::converters::deserialize_milliseconds_to_duration; use apollo_config::dumping::{prepend_sub_config_name, ser_param, SerializeConfig}; use apollo_config::{ParamPath, ParamPrivacyInput, SerializedParam}; use blockifier::blockifier::config::{ContractClassManagerConfig, WorkerPoolConfig}; @@ -18,6 +20,9 @@ pub struct BlockBuilderConfig { pub bouncer_config: BouncerConfig, pub n_concurrent_txs: usize, pub tx_polling_interval_millis: u64, + #[serde(deserialize_with = "deserialize_milliseconds_to_duration")] + // TODO(dan): add validation for this field. Probably should be bounded. + pub proposer_idle_detection_delay_millis: Duration, pub versioned_constants_overrides: VersionedConstantsOverrides, } @@ -30,6 +35,7 @@ impl Default for BlockBuilderConfig { bouncer_config: BouncerConfig::default(), n_concurrent_txs: 100, tx_polling_interval_millis: 10, + proposer_idle_detection_delay_millis: Duration::from_millis(2000), versioned_constants_overrides: VersionedConstantsOverrides::default(), } } @@ -53,6 +59,14 @@ impl SerializeConfig for BlockBuilderConfig { request returned no transactions.", ParamPrivacyInput::Public, )])); + dump.append(&mut BTreeMap::from([ser_param( + "proposer_idle_detection_delay_millis", + &self.proposer_idle_detection_delay_millis.as_millis(), + "Minimum time (in milliseconds) that must pass since block creation started before \ + checking for idle state. If this delay has passed AND no transactions are currently \ + being executed, the proposer will finish building the current block.", + ParamPrivacyInput::Public, + )])); dump.append(&mut prepend_sub_config_name( self.versioned_constants_overrides.dump(), "versioned_constants_overrides", @@ -133,6 +147,7 @@ pub struct BatcherConfig { pub contract_class_manager_config: ContractClassManagerConfig, pub max_l1_handler_txs_per_block_proposal: usize, pub pre_confirmed_cende_config: PreconfirmedCendeConfig, + pub propose_l1_txs_every: u64, } impl SerializeConfig for BatcherConfig { @@ -158,6 +173,12 @@ impl SerializeConfig for BatcherConfig { "The maximum number of L1 handler transactions to include in a block proposal.", ParamPrivacyInput::Public, ), + ser_param( + "propose_l1_txs_every", + &self.propose_l1_txs_every, + "Only propose L1 transactions every N proposals.", + ParamPrivacyInput::Public, + ), ]); dump.append(&mut prepend_sub_config_name(self.storage.dump(), "storage")); dump.append(&mut prepend_sub_config_name( @@ -200,6 +221,7 @@ impl Default for BatcherConfig { contract_class_manager_config: ContractClassManagerConfig::default(), max_l1_handler_txs_per_block_proposal: 3, pre_confirmed_cende_config: PreconfirmedCendeConfig::default(), + propose_l1_txs_every: 1, // Default is to propose L1 transactions every proposal. } } } diff --git a/crates/apollo_batcher_types/src/batcher_types.rs b/crates/apollo_batcher_types/src/batcher_types.rs index 25c5e2497d0..dbb4c14c52d 100644 --- a/crates/apollo_batcher_types/src/batcher_types.rs +++ b/crates/apollo_batcher_types/src/batcher_types.rs @@ -72,7 +72,6 @@ pub enum GetProposalContent { } #[derive(Clone, Debug, Serialize, Deserialize)] -// TODO(Dan): Consider unifying with BuildProposalInput as they have the same fields. pub struct ValidateBlockInput { pub proposal_id: ProposalId, pub deadline: chrono::DateTime, diff --git a/crates/apollo_central_sync_config/src/config.rs b/crates/apollo_central_sync_config/src/config.rs index 5c0904f25e1..f270d23942a 100644 --- a/crates/apollo_central_sync_config/src/config.rs +++ b/crates/apollo_central_sync_config/src/config.rs @@ -23,7 +23,7 @@ pub struct CentralSourceConfig { pub max_state_updates_to_download: usize, pub max_state_updates_to_store_in_memory: usize, pub max_classes_to_download: usize, - // TODO(dan): validate that class_cache_size is a positive integer. + #[validate(range(min = 1))] pub class_cache_size: usize, pub retry_config: RetryConfig, } diff --git a/crates/apollo_class_manager/src/class_manager_test.rs b/crates/apollo_class_manager/src/class_manager_test.rs index df4c8d3cd4e..e039121e1f1 100644 --- a/crates/apollo_class_manager/src/class_manager_test.rs +++ b/crates/apollo_class_manager/src/class_manager_test.rs @@ -12,12 +12,16 @@ use starknet_api::felt; use starknet_api::state::SierraContractClass; use crate::class_manager::ClassManager; -use crate::class_storage::{create_tmp_dir, FsClassStorage}; +use crate::class_storage::FsClassStorage; impl ClassManager { fn new_for_testing(compiler: MockSierraCompilerClient, config: ClassManagerConfig) -> Self { + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(persistent_root.path()).unwrap(); + std::fs::create_dir_all(class_hash_storage_path_prefix.path()).unwrap(); let storage = - FsClassStorage::new_for_testing(&create_tmp_dir().unwrap(), &create_tmp_dir().unwrap()); + FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); ClassManager::new(config, Arc::new(compiler), storage) } diff --git a/crates/apollo_class_manager/src/class_storage.rs b/crates/apollo_class_manager/src/class_storage.rs index 9e279f90d54..5cb5c5ac872 100644 --- a/crates/apollo_class_manager/src/class_storage.rs +++ b/crates/apollo_class_manager/src/class_storage.rs @@ -319,6 +319,7 @@ impl From for CachedClassStorageError impl FsClassStorage { pub fn new(config: FsClassStorageConfig) -> FsClassStorageResult { let class_hash_storage = ClassHashStorage::new(config.class_hash_storage_config)?; + std::fs::create_dir_all(&config.persistent_root)?; Ok(Self { persistent_root: config.persistent_root, class_hash_storage }) } @@ -370,6 +371,29 @@ impl FsClassStorage { concat_deprecated_executable_filename(&self.get_persistent_dir(class_id)) } + fn create_tmp_dir( + &self, + class_id: ClassId, + ) -> FsClassStorageResult<(tempfile::TempDir, PathBuf)> { + // Compute the final persistent directory for this `class_id` + let persistent_dir = self.get_persistent_dir(class_id); + let parent_dir = persistent_dir + .parent() + .expect("Class persistent dir should have a parent") + .to_path_buf(); + std::fs::create_dir_all(&parent_dir)?; + // Create a temporary directory under the parent of the final persistent directory to ensure + // `rename` will be atomic. + let tmp_root = tempfile::tempdir_in(&parent_dir)?; + // Get the leaf directory name of the final persistent directory. + let leaf = persistent_dir.file_name().expect("Class dir leaf should exist"); + // Create the temporary directory under the temporary root. + let tmp_dir = tmp_root.path().join(leaf); + // Returning `TempDir` since without it the handle would drop immediately and the temp + // directory would be removed before writes/rename. + Ok((tmp_root, tmp_dir)) + } + fn mark_class_id_as_existent( &mut self, class_id: ClassId, @@ -380,6 +404,7 @@ impl FsClassStorage { .set_executable_class_hash_v2(class_id, executable_class_hash_v2)?) } + #[allow(dead_code)] fn write_class( &self, class_id: ClassId, @@ -393,6 +418,7 @@ impl FsClassStorage { Ok(()) } + #[allow(dead_code)] fn write_deprecated_class( &self, class_id: ClassId, @@ -404,9 +430,6 @@ impl FsClassStorage { Ok(()) } - // TODO(Elin): restore use of `write_[deprecated_]class_atomically`, but tmpdir - // should be located inside the PVC to prevent linking errors. - #[allow(dead_code)] fn write_class_atomically( &self, class_id: ClassId, @@ -414,8 +437,7 @@ impl FsClassStorage { executable_class: RawExecutableClass, ) -> FsClassStorageResult<()> { // Write classes to a temporary directory. - let tmp_dir = create_tmp_dir()?; - let tmp_dir = tmp_dir.path().join(self.get_class_dir(class_id)); + let (_tmp_root, tmp_dir) = self.create_tmp_dir(class_id)?; class.write_to_file(concat_sierra_filename(&tmp_dir))?; executable_class.write_to_file(concat_executable_filename(&tmp_dir))?; @@ -426,15 +448,13 @@ impl FsClassStorage { Ok(()) } - #[allow(dead_code)] fn write_deprecated_class_atomically( &self, class_id: ClassId, class: RawExecutableClass, ) -> FsClassStorageResult<()> { // Write class to a temporary directory. - let tmp_dir = create_tmp_dir()?; - let tmp_dir = tmp_dir.path().join(self.get_class_dir(class_id)); + let (_tmp_root, tmp_dir) = self.create_tmp_dir(class_id)?; class.write_to_file(concat_deprecated_executable_filename(&tmp_dir))?; // Atomically rename directory to persistent one. @@ -453,15 +473,15 @@ impl ClassStorage for FsClassStorage { &mut self, class_id: ClassId, class: RawClass, - executable_class_hash: ExecutableClassHash, + executable_class_hash_v2: ExecutableClassHash, executable_class: RawExecutableClass, ) -> Result<(), Self::Error> { if self.contains_class(class_id)? { return Ok(()); } - self.write_class(class_id, class, executable_class)?; - self.mark_class_id_as_existent(class_id, executable_class_hash)?; + self.write_class_atomically(class_id, class, executable_class)?; + self.mark_class_id_as_existent(class_id, executable_class_hash_v2)?; Ok(()) } @@ -513,7 +533,7 @@ impl ClassStorage for FsClassStorage { return Ok(()); } - self.write_deprecated_class(class_id, class)?; + self.write_deprecated_class_atomically(class_id, class)?; Ok(()) } @@ -555,9 +575,3 @@ fn concat_executable_filename(path: &Path) -> PathBuf { fn concat_deprecated_executable_filename(path: &Path) -> PathBuf { path.join("deprecated_casm") } - -// Creates a tmp directory and returns a owned representation of it. -// As long as the returned directory object is lived, the directory is not deleted. -pub(crate) fn create_tmp_dir() -> FsClassStorageResult { - Ok(tempfile::tempdir()?) -} diff --git a/crates/apollo_class_manager/src/class_storage_test.rs b/crates/apollo_class_manager/src/class_storage_test.rs index ae157d1dcde..5d7a6694c6f 100644 --- a/crates/apollo_class_manager/src/class_storage_test.rs +++ b/crates/apollo_class_manager/src/class_storage_test.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use apollo_class_manager_config::config::{CachedClassStorageConfig, ClassHashDbConfig}; use apollo_class_manager_types::CachedClassStorageError; use apollo_compile_to_casm_types::{RawClass, RawExecutableClass}; @@ -8,7 +10,6 @@ use starknet_api::felt; use starknet_api::state::SierraContractClass; use crate::class_storage::{ - create_tmp_dir, CachedClassStorage, ClassHashStorage, ClassHashStorageConfig, @@ -42,14 +43,15 @@ impl FsClassStorage { class_hash_storage_path_prefix: &tempfile::TempDir, ) -> Self { let class_hash_storage = ClassHashStorage::new_for_testing(class_hash_storage_path_prefix); + std::fs::create_dir_all(persistent_root.path()).unwrap(); Self { persistent_root: persistent_root.path().to_path_buf(), class_hash_storage } } } #[test] fn fs_storage() { - let persistent_root = create_tmp_dir().unwrap(); - let class_hash_storage_path_prefix = create_tmp_dir().unwrap(); + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); let mut storage = FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); @@ -84,8 +86,8 @@ fn fs_storage() { #[test] fn fs_storage_deprecated_class_api() { - let persistent_root = create_tmp_dir().unwrap(); - let class_hash_storage_path_prefix = create_tmp_dir().unwrap(); + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); let mut storage = FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); @@ -105,13 +107,39 @@ fn fs_storage_deprecated_class_api() { storage.set_deprecated_class(class_id, executable_class).unwrap(); } +#[test] +fn temp_dir_location_and_atomic_write_layout() { + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); + let storage = + FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); + + let class_id = ClassHash(felt!("0x1234")); + let persistent_dir = storage.persistent_root.join({ + let hex = hex::encode(class_id.to_bytes_be()); + let (a, b) = (&hex[..2], &hex[2..4]); + PathBuf::from(a).join(b).join(hex) + }); + + // Create tmp dir via the atomic writer and ensure it resides under parent_dir. + let class = RawClass::try_from(SierraContractClass::default()).unwrap(); + let executable_class = + RawExecutableClass::try_from(ContractClass::test_casm_contract_class()).unwrap(); + storage.write_class_atomically(class_id, class.clone(), executable_class.clone()).unwrap(); + + // After atomic write and rename, the persistent dir should exist and contain the files. + assert!(persistent_dir.exists()); + assert!(persistent_dir.join("sierra").exists()); + assert!(persistent_dir.join("casm").exists()); +} + #[test] fn fs_storage_nonexistent_persistent_root_is_created() { - let parent_dir = create_tmp_dir().unwrap(); + let parent_dir = tempfile::tempdir().unwrap(); let nonexistent_root = parent_dir.path().join("nonexistent_root"); assert!(!nonexistent_root.exists()); - let class_hash_storage_path_prefix = create_tmp_dir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); let class_hash_storage = ClassHashStorage::new_for_testing(&class_hash_storage_path_prefix); let mut storage = FsClassStorage { persistent_root: nonexistent_root.clone(), class_hash_storage }; @@ -138,8 +166,8 @@ fn fs_storage_nonexistent_persistent_root_is_created() { // TODO(Elin): should this flow return an error? #[test] fn fs_storage_partial_write_only_atomic_marker() { - let persistent_root = create_tmp_dir().unwrap(); - let class_hash_storage_path_prefix = create_tmp_dir().unwrap(); + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); let mut storage = FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); @@ -156,8 +184,8 @@ fn fs_storage_partial_write_only_atomic_marker() { #[test] fn fs_storage_partial_write_no_atomic_marker() { - let persistent_root = create_tmp_dir().unwrap(); - let class_hash_storage_path_prefix = create_tmp_dir().unwrap(); + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); let storage = FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); @@ -239,8 +267,8 @@ fn cached_storage_cairo1_get_executable_and_hash() { #[test] fn cached_storage_cairo0_get_executable_and_no_hash() { - let persistent_root = create_tmp_dir().unwrap(); - let class_hash_storage_path_prefix = create_tmp_dir().unwrap(); + let persistent_root = tempfile::tempdir().unwrap(); + let class_hash_storage_path_prefix = tempfile::tempdir().unwrap(); let mut fs_storage = FsClassStorage::new_for_testing(&persistent_root, &class_hash_storage_path_prefix); diff --git a/crates/apollo_class_manager/src/lib.rs b/crates/apollo_class_manager/src/lib.rs index 8ecc078777a..3947c4eee0f 100644 --- a/crates/apollo_class_manager/src/lib.rs +++ b/crates/apollo_class_manager/src/lib.rs @@ -1,10 +1,19 @@ pub mod class_manager; -pub mod class_storage; +mod class_storage; pub mod communication; pub mod metrics; +// Re-export selected items from the now-private class_storage module. +pub use class_storage::{ + CachedClassStorage, + ClassHashStorage, + ClassHashStorageError, + ClassStorage, + FsClassStorage, + FsClassStorageError, +}; + use crate::class_manager::ClassManager as GenericClassManager; -use crate::class_storage::FsClassStorage; pub struct FsClassManager(pub GenericClassManager); diff --git a/crates/apollo_compile_to_casm/src/compile_test.rs b/crates/apollo_compile_to_casm/src/compile_test.rs index dbc612d50ba..dac185cb80c 100644 --- a/crates/apollo_compile_to_casm/src/compile_test.rs +++ b/crates/apollo_compile_to_casm/src/compile_test.rs @@ -25,6 +25,7 @@ use crate::{RawClass, SierraCompiler}; const SIERRA_COMPILATION_CONFIG: SierraCompilationConfig = SierraCompilationConfig { max_bytecode_size: DEFAULT_MAX_BYTECODE_SIZE, max_memory_usage: None, + audited_libfuncs_only: false, }; fn compiler() -> SierraToCasmCompiler { @@ -75,6 +76,7 @@ fn test_max_bytecode_size() { let compiler = SierraToCasmCompiler::new(SierraCompilationConfig { max_bytecode_size: expected_casm_bytecode_length, max_memory_usage: None, + audited_libfuncs_only: false, }); let casm_contract_class = compiler .compile(contract_class.clone()) @@ -85,6 +87,7 @@ fn test_max_bytecode_size() { let compiler = SierraToCasmCompiler::new(SierraCompilationConfig { max_bytecode_size: expected_casm_bytecode_length - 1, max_memory_usage: None, + audited_libfuncs_only: false, }); let result = compiler.compile(contract_class); assert_matches!(result, Err(CompilationUtilError::CompilationError(string)) @@ -139,16 +142,14 @@ fn test_max_memory_usage() { let contract_class = get_test_contract(); // Compile the contract class without any memory usage limit to get the expected output. - let compiler = SierraToCasmCompiler::new(SierraCompilationConfig { - max_bytecode_size: DEFAULT_MAX_BYTECODE_SIZE, - max_memory_usage: None, - }); + let compiler = compiler(); let expected_executable_class = compiler.compile(contract_class.clone()).unwrap(); // Positive flow. let compiler = SierraToCasmCompiler::new(SierraCompilationConfig { max_bytecode_size: DEFAULT_MAX_BYTECODE_SIZE, max_memory_usage: Some(DEFAULT_MAX_MEMORY_USAGE), + audited_libfuncs_only: false, }); let executable_class = compiler.compile(contract_class.clone()).unwrap(); assert_eq!(executable_class, expected_executable_class); @@ -157,6 +158,7 @@ fn test_max_memory_usage() { let compiler = SierraToCasmCompiler::new(SierraCompilationConfig { max_bytecode_size: DEFAULT_MAX_BYTECODE_SIZE, max_memory_usage: Some(8 * 1024 * 1024), + audited_libfuncs_only: false, }); let compilation_result = compiler.compile(contract_class); assert_matches!(compilation_result, Err(CompilationUtilError::CompilationError(string)) diff --git a/crates/apollo_compile_to_casm/src/compiler.rs b/crates/apollo_compile_to_casm/src/compiler.rs index 91d2e12a8b7..638dadbb3e4 100644 --- a/crates/apollo_compile_to_casm/src/compiler.rs +++ b/crates/apollo_compile_to_casm/src/compiler.rs @@ -33,9 +33,8 @@ impl SierraToCasmCompiler { "--add-pythonic-hints", "--max-bytecode-size", &self.config.max_bytecode_size.to_string(), - // TODO(Shahak, Elin): Fix this in a safe way. "--allowed-libfuncs-list-name", - "audited", + if self.config.audited_libfuncs_only { "audited" } else { "all" }, ]; let resource_limits = ResourceLimits::new(None, None, self.config.max_memory_usage); diff --git a/crates/apollo_compile_to_casm_types/Cargo.toml b/crates/apollo_compile_to_casm_types/Cargo.toml index 79d88aaee01..a50e5dd696c 100644 --- a/crates/apollo_compile_to_casm_types/Cargo.toml +++ b/crates/apollo_compile_to_casm_types/Cargo.toml @@ -25,4 +25,7 @@ strum_macros.workspace = true thiserror.workspace = true [dev-dependencies] +blockifier_test_utils.workspace = true +expect-test.workspace = true mockall.workspace = true +starknet_api = { workspace = true, features = ["testing"] } diff --git a/crates/apollo_compile_to_casm_types/src/lib.rs b/crates/apollo_compile_to_casm_types/src/lib.rs index 7d35e1632f5..fd0cc50198c 100644 --- a/crates/apollo_compile_to_casm_types/src/lib.rs +++ b/crates/apollo_compile_to_casm_types/src/lib.rs @@ -20,6 +20,10 @@ use strum::{EnumVariantNames, VariantNames}; use strum_macros::{AsRefStr, EnumDiscriminants, EnumIter, IntoStaticStr}; use thiserror::Error; +#[cfg(test)] +#[path = "test.rs"] +pub mod test; + pub type SierraCompilerResult = Result; pub type SierraCompilerClientResult = Result; @@ -48,6 +52,30 @@ pub enum RawClassError { WriteError(#[from] serde_json::Error), } +struct CounterWriter { + size_counter: usize, +} + +impl std::io::Write for CounterWriter { + fn write(&mut self, buf: &[u8]) -> Result { + self.size_counter += buf.len(); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(()) + } +} + +fn size_of_serialized(value: &T) -> Result +where + T: ?Sized + Serialize, +{ + let mut counter = CounterWriter { size_counter: 0 }; + serde_json::to_writer(&mut counter, value)?; + Ok(counter.size_counter) +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SerializedClass(Arc, std::marker::PhantomData); @@ -57,7 +85,7 @@ impl SerializedClass { } pub fn size(&self) -> RawClassResult { - Ok(serde_json::to_string_pretty(&*self.0)?.len()) + Ok(size_of_serialized(&self.0)?) } fn new(value: serde_json::Value) -> Self { diff --git a/crates/apollo_compile_to_casm_types/src/test.rs b/crates/apollo_compile_to_casm_types/src/test.rs new file mode 100644 index 00000000000..d2b0db59bd8 --- /dev/null +++ b/crates/apollo_compile_to_casm_types/src/test.rs @@ -0,0 +1,58 @@ +use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1}; +use blockifier_test_utils::contracts::FeatureContract; +use expect_test::expect; +use starknet_api::contract_class::{ContractClass, SierraVersion}; + +use crate::{size_of_serialized, RawClass, RawExecutableClass}; + +#[test] +fn test_size_of_serialized() { + let value = serde_json::json!({ + "a": 1, + "b": "hello", + "c": [1, 2, 3], + }); + + let size = size_of_serialized(&value).unwrap(); + let serialized_size = serde_json::to_vec(&value).unwrap().len(); + + assert_eq!(size, serialized_size); + expect!["31"].assert_eq(&size.to_string()); +} + +#[test] +fn compact_serialization() { + const EXPECTED: &str = "{\"V1\":[{\"bytecode\":[\"0x1\",\"0x1\",\"0x1\"],\"compiler_version\":\ + \"\",\"entry_points_by_type\":{\"CONSTRUCTOR\":[],\"EXTERNAL\":[],\"\ + L1_HANDLER\":[]},\"hints\":[],\"prime\":\"0x0\"},\"1.7.0\"]}"; + let raw_executable_class = + RawExecutableClass::try_from(ContractClass::test_casm_contract_class()).unwrap(); + let serialized = serde_json::to_string(&raw_executable_class.0).unwrap(); + + assert_eq!(serialized, EXPECTED); + assert_eq!(raw_executable_class.size().unwrap(), EXPECTED.len()); +} + +#[test] +fn consistent_serialization_size() { + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)); + + let sierra_class = test_contract.get_sierra(); + let sierra_class_length = serde_json::to_vec(&sierra_class).unwrap().len(); + + let raw_sierra_class: RawClass = sierra_class.try_into().unwrap(); + let raw_sierra_class_size = raw_sierra_class.size().unwrap(); + + assert_eq!(raw_sierra_class_size, sierra_class_length); + + let casm_class = ContractClass::V1(( + serde_json::from_str(&test_contract.get_raw_class()).unwrap(), + SierraVersion::LATEST, + )); + let casm_class_length = serde_json::to_vec(&casm_class).unwrap().len(); + + let raw_casm_class = RawExecutableClass::try_from(casm_class).unwrap(); + let raw_casm_class_size = raw_casm_class.size().unwrap(); + + assert_eq!(raw_casm_class_size, casm_class_length); +} diff --git a/crates/apollo_compile_to_native/build.rs b/crates/apollo_compile_to_native/build.rs index 679c5001f0b..3fc1bee6148 100644 --- a/crates/apollo_compile_to_native/build.rs +++ b/crates/apollo_compile_to_native/build.rs @@ -16,7 +16,7 @@ fn install_starknet_native_compile() { let binary_name = CAIRO_NATIVE_BINARY_NAME; let required_version = REQUIRED_CAIRO_NATIVE_VERSION; - let cargo_install_args = &["cairo-native", "--version", required_version, "--bin", binary_name]; + let cargo_install_args = &[binary_name, "--version", required_version]; install_compiler_binary(binary_name, required_version, cargo_install_args, &out_dir()); } diff --git a/crates/apollo_compile_to_native/src/constants.rs b/crates/apollo_compile_to_native/src/constants.rs index 02cf2eeaabd..bbb4531b713 100644 --- a/crates/apollo_compile_to_native/src/constants.rs +++ b/crates/apollo_compile_to_native/src/constants.rs @@ -4,4 +4,4 @@ pub(crate) const CAIRO_NATIVE_BINARY_NAME: &str = "starknet-native-compile"; -pub const REQUIRED_CAIRO_NATIVE_VERSION: &str = "0.6.2"; +pub const REQUIRED_CAIRO_NATIVE_VERSION: &str = "0.7.1"; diff --git a/crates/apollo_config/README.md b/crates/apollo_config/README.md index a5a0f8a97e2..97e08ce8511 100644 --- a/crates/apollo_config/README.md +++ b/crates/apollo_config/README.md @@ -1,8 +1,8 @@ -# papyrus-config +# apollo-config ## Description -papyrus-config is a flexible and powerful layered configuration system designed specifically for Papyrus, a Starknet node. This system allows you to easily manage configurations for your Papyrus node by leveraging various sources and providing additional helpful features. +apollo-config is a flexible and powerful layered configuration system designed specifically for Apollo, a Starknet node. This system allows you to easily manage configurations for your Apollo sequencer by leveraging various sources and providing additional helpful features. ## Configuration sources @@ -27,8 +27,8 @@ Supports multiple configuration sources in ascending order of overriding priorit Developer reference documentation is available at https://docs.rs/apollo_config/. The documentation on this site is updated periodically. -To view the most up-to-date documentation, enter the following command at the root directory of the `papyrus` project: +To view the most up-to-date documentation, enter the following command at the root directory of the project: ```shell cargo doc --open -p apollo_config -``` \ No newline at end of file +``` diff --git a/crates/apollo_config/src/converters.rs b/crates/apollo_config/src/converters.rs index 22f23ce6de0..687b2fada32 100644 --- a/crates/apollo_config/src/converters.rs +++ b/crates/apollo_config/src/converters.rs @@ -262,7 +262,7 @@ where T: FromStr, T::Err: std::fmt::Display, { - let raw: String = ::deserialize(de)?; + let raw = String::deserialize(de)?; if raw.trim().is_empty() { return Ok(Vec::new()); @@ -272,3 +272,41 @@ where .map(|s| T::from_str(s).map_err(|e| D::Error::custom(format!("Invalid value '{s}': {e}")))) .collect() } + +/// Serializes an optional list into a comma-separated string. +/// Returns `None` if the input is `None`. +pub fn serialize_optional_comma_separated(list: &Option>) -> Option +where + T: ToString, +{ + match list { + None => None, + Some(list) => Some(list.iter().map(|item| item.to_string()).collect::>().join(",")), + } +} + +/// Deserializes an optional comma-separated list of values implementing `FromStr` into +/// `Option>`. Returns `None` for empty or missing strings. +pub fn deserialize_comma_separated_str<'de, D, T>(de: D) -> Result>, D::Error> +where + D: Deserializer<'de>, + T: FromStr, + ::Err: std::fmt::Display, +{ + let raw = String::deserialize(de).unwrap_or_default(); + if raw.trim().is_empty() { + return Ok(None); + } + + let mut output: Vec = Vec::new(); + for part in raw.split(',').filter(|s| !s.is_empty()) { + let value = T::from_str(part) + .map_err(|e| D::Error::custom(format!("Invalid value '{part}': {e}")))?; + output.push(value); + } + + if output.is_empty() { + return Ok(None); + } + Ok(Some(output)) +} diff --git a/crates/apollo_config/src/dumping.rs b/crates/apollo_config/src/dumping.rs index 96e2960869a..06457369ade 100644 --- a/crates/apollo_config/src/dumping.rs +++ b/crates/apollo_config/src/dumping.rs @@ -173,7 +173,7 @@ pub trait SerializeConfig { ) -> Result<(), ConfigError> { let combined_map = combine_config_map_and_pointers(self.dump(), config_pointers, non_pointer_params)?; - serialize_to_file(combined_map, file_path); + serialize_to_file(&combined_map, file_path); Ok(()) } } diff --git a/crates/apollo_config_manager/Cargo.toml b/crates/apollo_config_manager/Cargo.toml index 95d1bf65a4c..34be6f69a15 100644 --- a/crates/apollo_config_manager/Cargo.toml +++ b/crates/apollo_config_manager/Cargo.toml @@ -12,6 +12,7 @@ testing = [] workspace = true [dependencies] +apollo_config.workspace = true apollo_config_manager_config.workspace = true apollo_config_manager_types.workspace = true apollo_consensus_config.workspace = true @@ -30,3 +31,4 @@ apollo_config_manager_types = { workspace = true, features = ["testing"] } apollo_node_config = { workspace = true, features = ["testing"] } starknet_api.workspace = true tempfile.workspace = true +tracing-test.workspace = true diff --git a/crates/apollo_config_manager/src/config_manager.rs b/crates/apollo_config_manager/src/config_manager.rs index d5344eac543..1752b5419c8 100644 --- a/crates/apollo_config_manager/src/config_manager.rs +++ b/crates/apollo_config_manager/src/config_manager.rs @@ -6,7 +6,7 @@ use apollo_infra::component_definitions::{ComponentRequestHandler, ComponentStar use apollo_mempool_config::config::MempoolDynamicConfig; use apollo_node_config::node_config::NodeDynamicConfig; use async_trait::async_trait; -use tracing::{info, instrument}; +use tracing::info; #[cfg(test)] #[path = "config_manager_tests.rs"] @@ -45,7 +45,6 @@ impl ConfigManager { #[async_trait] impl ComponentRequestHandler for ConfigManager { - #[instrument(skip(self), ret)] async fn handle_request(&mut self, request: ConfigManagerRequest) -> ConfigManagerResponse { match request { // TODO(Nadin/Tsabary): consider using a macro to generate the responses for each type diff --git a/crates/apollo_config_manager/src/config_manager_runner.rs b/crates/apollo_config_manager/src/config_manager_runner.rs index bb0fc096c00..8a12efb06b9 100644 --- a/crates/apollo_config_manager/src/config_manager_runner.rs +++ b/crates/apollo_config_manager/src/config_manager_runner.rs @@ -1,5 +1,7 @@ +use std::collections::BTreeSet; use std::future::pending; +use apollo_config::presentation::get_config_presentation; use apollo_config_manager_config::config::ConfigManagerConfig; use apollo_config_manager_types::communication::SharedConfigManagerClient; use apollo_infra::component_definitions::{default_component_start_fn, ComponentStarter}; @@ -7,6 +9,7 @@ use apollo_infra::component_server::WrapperServer; use apollo_node_config::config_utils::load_and_validate_config; use apollo_node_config::node_config::NodeDynamicConfig; use async_trait::async_trait; +use serde_json::Value; use tokio::time::{interval, Duration as TokioDuration}; use tracing::{error, info}; @@ -17,6 +20,7 @@ pub mod config_manager_runner_tests; pub struct ConfigManagerRunner { config_manager_config: ConfigManagerConfig, config_manager_client: SharedConfigManagerClient, + latest_node_dynamic_config: NodeDynamicConfig, cli_args: Vec, } @@ -50,30 +54,66 @@ impl ConfigManagerRunner { pub fn new( config_manager_config: ConfigManagerConfig, config_manager_client: SharedConfigManagerClient, + initial_node_dynamic_config: NodeDynamicConfig, cli_args: Vec, ) -> Self { - Self { config_manager_config, config_manager_client, cli_args } + Self { + config_manager_config, + config_manager_client, + latest_node_dynamic_config: initial_node_dynamic_config, + cli_args, + } } // TODO(Nadin): Define a proper result type instead of Box pub(crate) async fn update_config( - &self, + &mut self, ) -> Result> { let config = load_and_validate_config(self.cli_args.clone())?; let node_dynamic_config = NodeDynamicConfig::from(&config); - info!("Extracted NodeDynamicConfig: {:?}", node_dynamic_config); - // TODO(Nadin/Tsabary): Store the last loaded config, compare for changes and only send the - // changes to the config manager. - match self.config_manager_client.set_node_dynamic_config(node_dynamic_config.clone()).await - { - Ok(()) => { - info!("Successfully updated dynamic config"); - Ok(node_dynamic_config) + // Compare the previous and the newly read node dynamic config. + if self.latest_node_dynamic_config == node_dynamic_config { + // No change, so no action is needed. + Ok(node_dynamic_config) + } else { + // Log the diff between the latest and the new node dynamic config. + self.log_config_diff(&self.latest_node_dynamic_config, &node_dynamic_config); + // Update the latest node dynamic config. + self.latest_node_dynamic_config = node_dynamic_config.clone(); + match self + .config_manager_client + .set_node_dynamic_config(node_dynamic_config.clone()) + .await + { + Ok(()) => { + info!("Successfully updated dynamic config"); + Ok(node_dynamic_config) + } + Err(e) => { + error!("Failed to update dynamic config: {:?}", e); + Err(format!("Failed to update dynamic config: {:?}", e).into()) + } } - Err(e) => { - error!("Failed to update dynamic config: {:?}", e); - Err(format!("Failed to update dynamic config: {:?}", e).into()) + } + } + + fn log_config_diff(&self, old_config: &NodeDynamicConfig, new_config: &NodeDynamicConfig) { + let old_config = get_config_presentation(old_config, false).unwrap(); + let new_config = get_config_presentation(new_config, false).unwrap(); + let all_keys: BTreeSet<_> = old_config + .as_object() + .unwrap() + .keys() + .chain(new_config.as_object().unwrap().keys()) + .collect(); + + for key in all_keys { + let old_value = old_config.as_object().unwrap().get(key).unwrap_or(&Value::Null); + let new_value = new_config.as_object().unwrap().get(key).unwrap_or(&Value::Null); + + if old_value != new_value { + info!("ConfigManagerRunner: {key} changed from {old_value} to {new_value}"); } } } diff --git a/crates/apollo_config_manager/src/config_manager_runner_tests.rs b/crates/apollo_config_manager/src/config_manager_runner_tests.rs index f35ad2cc240..0ca15b915c2 100644 --- a/crates/apollo_config_manager/src/config_manager_runner_tests.rs +++ b/crates/apollo_config_manager/src/config_manager_runner_tests.rs @@ -7,9 +7,11 @@ use apollo_config_manager_types::communication::{ MockConfigManagerClient, SharedConfigManagerClient, }; +use apollo_consensus_config::config::ConsensusDynamicConfig; use apollo_node_config::config_utils::DeploymentBaseAppConfig; use apollo_node_config::definitions::ConfigPointersMap; use apollo_node_config::node_config::{ + NodeDynamicConfig, SequencerNodeConfig, CONFIG_NON_POINTERS_WHITELIST, CONFIG_POINTERS, @@ -17,6 +19,7 @@ use apollo_node_config::node_config::{ use serde_json::Value; use starknet_api::core::ContractAddress; use tempfile::NamedTempFile; +use tracing_test::traced_test; use crate::config_manager_runner::ConfigManagerRunner; @@ -86,7 +89,7 @@ fn update_config_file(temp_file: &NamedTempFile) -> String { } #[tokio::test] -async fn test_config_manager_runner_update_config_with_changed_values() { +async fn config_manager_runner_update_config_with_changed_values() { // Set a mock config manager client to expect the update dynamic config request. let mut mock_client = MockConfigManagerClient::new(); mock_client.expect_set_node_dynamic_config().times(1..).return_const(Ok(())); @@ -98,9 +101,15 @@ async fn test_config_manager_runner_update_config_with_changed_values() { // Create a temporary config file and get the validator id value. let (temp_file, cli_args, validator_id_value) = create_temp_config_file_and_args(); + let node_dynamic_config = NodeDynamicConfig::default(); + // Create a config manager runner and update the config. - let config_manager_runner = - ConfigManagerRunner::new(config_manager_config, config_manager_client, cli_args); + let mut config_manager_runner = ConfigManagerRunner::new( + config_manager_config, + config_manager_client, + node_dynamic_config, + cli_args, + ); // Helper function to convert a hex string to a u128. fn hex_to_u128(s: &str) -> u128 { @@ -137,3 +146,37 @@ async fn test_config_manager_runner_update_config_with_changed_values() { expected_validator_id ); } + +#[traced_test] +#[test] +fn log_config_diff_changes() { + let old_dynamic_config = NodeDynamicConfig { + consensus_dynamic_config: Some(ConsensusDynamicConfig { + validator_id: ContractAddress::from(1u128), + ..Default::default() + }), + ..Default::default() + }; + + let new_dynamic_config = NodeDynamicConfig { + consensus_dynamic_config: Some(ConsensusDynamicConfig { + validator_id: ContractAddress::from(2u128), + ..Default::default() + }), + ..Default::default() + }; + + let mock_client = MockConfigManagerClient::new(); + let runner = ConfigManagerRunner::new( + ConfigManagerConfig::default(), + Arc::new(mock_client), + old_dynamic_config.clone(), + Vec::::new(), + ); + + runner.log_config_diff(&old_dynamic_config, &new_dynamic_config); + + assert!(logs_contain("consensus_dynamic_config changed from")); + assert!(logs_contain(r#""validator_id":"0x1""#)); + assert!(logs_contain(r#""validator_id":"0x2""#)); +} diff --git a/crates/apollo_config_manager/src/config_manager_tests.rs b/crates/apollo_config_manager/src/config_manager_tests.rs index e3d7ba29fb6..201d6abe57d 100644 --- a/crates/apollo_config_manager/src/config_manager_tests.rs +++ b/crates/apollo_config_manager/src/config_manager_tests.rs @@ -6,7 +6,7 @@ use apollo_node_config::node_config::NodeDynamicConfig; use crate::config_manager::ConfigManager; #[tokio::test] -async fn test_config_manager_update_config() { +async fn config_manager_update_config() { // Set a config manager. let config = ConfigManagerConfig::default(); @@ -31,7 +31,7 @@ async fn test_config_manager_update_config() { // Set a new dynamic config by creating a new consensus dynamic config. For simplicity, we // create an arbitrary one and assert it's not the default one. let new_consensus_dynamic_config = - ConsensusDynamicConfig { validator_id: ValidatorId::from(1_u8) }; + ConsensusDynamicConfig { validator_id: ValidatorId::from(1_u8), ..Default::default() }; assert_ne!( consensus_dynamic_config, new_consensus_dynamic_config, "Consensus dynamic config should be different: {consensus_dynamic_config:#?} != {:#?}", diff --git a/crates/apollo_consensus/Cargo.toml b/crates/apollo_consensus/Cargo.toml index 429fe30bd74..1dd050bd263 100644 --- a/crates/apollo_consensus/Cargo.toml +++ b/crates/apollo_consensus/Cargo.toml @@ -10,6 +10,7 @@ description = "Reach consensus for Starknet" testing = [] [dependencies] +apollo_config_manager_types.workspace = true apollo_consensus_config.workspace = true apollo_infra_utils.workspace = true apollo_metrics.workspace = true @@ -32,12 +33,14 @@ tokio = { workspace = true, features = ["sync"] } tracing.workspace = true [dev-dependencies] +apollo_config_manager_types = { workspace = true, features = ["testing"] } apollo_network = { workspace = true, features = ["testing"] } apollo_network_types = { workspace = true, features = ["testing"] } apollo_storage = { workspace = true, features = ["testing"] } apollo_test_utils.workspace = true enum-as-inner.workspace = true mockall.workspace = true +rstest.workspace = true test-case.workspace = true [lints] diff --git a/crates/apollo_consensus/README.md b/crates/apollo_consensus/README.md deleted file mode 100644 index 35c2230a1ba..00000000000 --- a/crates/apollo_consensus/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# papyrus-consensus - -This crate provides an implementation of consensus for a Starknet node. - -### Disclaimer -This crate is still under development and is not keeping backwards compatibility with previous -versions. Breaking changes are expected to happen in the near future. - -## How to run -1. You must turn consensus on and provide a validator ID by passing: `--consensus.#is_none false --consensus.validator_id 0x` -2. Start by running any nodes which are validators for `consensus.start_height` which is by default 0 to avoid them missing the proposal. - 1. You can change the default number of validators by passing: `--consensus.num_validators ` - 2. You can change the default topic by passing: `--consensus.topic "TOPIC"` - 3. You can test the consensus under simulated network conditions, by passing: `--consensus.test.#is_none false` - 1. Optional arguments: - `--consensus.test.cache_size ` - `--consensus.test.random_seed ` - `--consensus.test.drop_probability 0` (set to 0 for now) - `--consensus.test.invalid_probability ` (0 to 1) - -#### Bootstrap Node -This must be run first: -``` -cargo run --package papyrus_node --bin papyrus_node -- --base_layer_url --network.#is_none false --consensus.#is_none false --consensus.validator_id 0x1 --storage.db_config.path_prefix -``` -- This will log `local_peer_id` which is used by other nodes. (Alternatively pass `network.secret_key` to have a fixed peer id). - -#### Other Nodes -Run each of the other nodes separately, using different `consensus.validator_id` {`0x2`, `0x3`, `0x0`}: - -``` -cargo run --package papyrus_node --bin papyrus_node -- --base_layer_url --network.#is_none false --consensus.#is_none false --consensus.validator_id 0x --network.port --network.bootstrap_peer_multiaddr.#is_none false --rpc.server_address 127.0.0.1: --monitoring_gateway.server_address 127.0.0.1: --storage.db_config.path_prefix --network.bootstrap_peer_multiaddr /ip4/127.0.0.1/tcp/10000/p2p/ -``` -- Node 0 is the first proposer and should be run last. - -UNIQUE - a value unique among all nodes running locally. diff --git a/crates/apollo_consensus/src/manager.rs b/crates/apollo_consensus/src/manager.rs index 5d570f5b348..71b37f81a38 100644 --- a/crates/apollo_consensus/src/manager.rs +++ b/crates/apollo_consensus/src/manager.rs @@ -10,14 +10,14 @@ mod manager_test; use std::collections::BTreeMap; -use std::time::Duration; -use apollo_consensus_config::config::TimeoutsConfig; +use apollo_config_manager_types::communication::SharedConfigManagerClient; +use apollo_consensus_config::config::{ConsensusConfig, ConsensusDynamicConfig}; use apollo_network::network_manager::BroadcastTopicClientTrait; use apollo_network_types::network_types::BroadcastedMessageMetadata; use apollo_protobuf::consensus::{ProposalInit, Vote}; use apollo_protobuf::converters::ProtobufConversionError; -use apollo_time::time::{sleep_until, Clock, DefaultClock}; +use apollo_time::time::{Clock, ClockExt, DefaultClock}; use futures::channel::mpsc; use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; @@ -28,32 +28,42 @@ use crate::metrics::{ register_metrics, CONSENSUS_BLOCK_NUMBER, CONSENSUS_CACHED_VOTES, + CONSENSUS_DECISIONS_REACHED_AS_PROPOSER, CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS, CONSENSUS_DECISIONS_REACHED_BY_SYNC, CONSENSUS_MAX_CACHED_BLOCK_NUMBER, CONSENSUS_PROPOSALS_RECEIVED, }; use crate::single_height_consensus::{ShcReturn, SingleHeightConsensus}; -use crate::types::{BroadcastVoteChannel, ConsensusContext, ConsensusError, Decision, ValidatorId}; +use crate::types::{BroadcastVoteChannel, ConsensusContext, ConsensusError, Decision}; use crate::votes_threshold::QuorumType; /// Arguments for running consensus. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct RunConsensusArguments { + /// Consensus configuration (static + dynamic). Static fields are used directly; dynamic + /// fields are refreshed at height boundaries via `config_manager_client` when provided. + pub consensus_config: ConsensusConfig, /// The height at which the node may participate in consensus (if it is a validator). pub start_active_height: BlockNumber, /// The height at which the node begins to run consensus. pub start_observe_height: BlockNumber, - /// The ID of this node. - pub validator_id: ValidatorId, - /// Delay before starting consensus; allowing the network to connect to peers. - pub consensus_delay: Duration, - /// The timeouts for the consensus algorithm. - pub timeouts: TimeoutsConfig, - /// The interval to wait between sync retries. - pub sync_retry_interval: Duration, /// Set to Byzantine by default. Using Honest means we trust all validators. Use with caution! pub quorum_type: QuorumType, + /// Optional client for fetching dynamic consensus config between heights. + pub config_manager_client: Option, +} + +impl std::fmt::Debug for RunConsensusArguments { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RunConsensusArguments") + .field("start_active_height", &self.start_active_height) + .field("start_observe_height", &self.start_observe_height) + .field("dynamic_config", &self.consensus_config.dynamic_config) + .field("static_config", &self.consensus_config.static_config) + .field("quorum_type", &self.quorum_type) + .finish() + } } /// Run consensus indefinitely. @@ -70,7 +80,11 @@ pub struct RunConsensusArguments { /// - `proposals_receiver`: The channel to receive proposals from the network. Proposals are /// represented as streams (ProposalInit, Content.*, ProposalFin). // Always print the validator ID since some tests collate multiple consensus logs in a single file. -#[instrument(skip_all, fields(validator_id=%run_consensus_args.validator_id), level = "error")] +#[instrument( + skip_all, + fields(validator_id=%run_consensus_args.consensus_config.dynamic_config.validator_id), + level = "error" +)] pub async fn run_consensus( run_consensus_args: RunConsensusArguments, mut context: ContextT, @@ -83,16 +97,29 @@ where info!("Running consensus, args: {:?}", run_consensus_args.clone()); register_metrics(); // Add a short delay to allow peers to connect and avoid "InsufficientPeers" error - tokio::time::sleep(run_consensus_args.consensus_delay).await; + tokio::time::sleep(run_consensus_args.consensus_config.static_config.startup_delay).await; + + // Ensure the node begins observing consensus no later than it becomes active. assert!(run_consensus_args.start_observe_height <= run_consensus_args.start_active_height); + let mut current_height = run_consensus_args.start_observe_height; let mut manager = MultiHeightManager::new( - run_consensus_args.validator_id, - run_consensus_args.sync_retry_interval, + run_consensus_args.consensus_config.clone(), run_consensus_args.quorum_type, - run_consensus_args.timeouts, ); loop { + if let Some(client) = &run_consensus_args.config_manager_client { + match client.get_consensus_dynamic_config().await { + Ok(dynamic_cfg) => { + manager.set_dynamic_config(dynamic_cfg); + } + Err(e) => { + error!( + "get_consensus_dynamic_config failed: {e}. Using previous dynamic config." + ); + } + } + } let must_observer = current_height < run_consensus_args.start_active_height; match manager .run_height( @@ -109,6 +136,10 @@ where // precommits to print. let round = decision.precommits[0].round; let proposer = context.proposer(current_height, round); + + if proposer == run_consensus_args.consensus_config.dynamic_config.validator_id { + CONSENSUS_DECISIONS_REACHED_AS_PROPOSER.increment(1); + } info!( "DECISION_REACHED: Decision reached for round {} with proposer {}. {:?}", round, proposer, decision @@ -141,33 +172,29 @@ type ProposalReceiverTuple = (ProposalInit, mpsc::Receiver); /// part of the single height consensus algorithm (e.g. messages from future heights). #[derive(Debug, Default)] struct MultiHeightManager { - validator_id: ValidatorId, + consensus_config: ConsensusConfig, future_votes: BTreeMap>, - sync_retry_interval: Duration, quorum_type: QuorumType, // Mapping: { Height : { Round : (Init, Receiver)}} cached_proposals: BTreeMap>>, - timeouts: TimeoutsConfig, } impl MultiHeightManager { /// Create a new consensus manager. - pub(crate) fn new( - validator_id: ValidatorId, - sync_retry_interval: Duration, - quorum_type: QuorumType, - timeouts: TimeoutsConfig, - ) -> Self { + pub(crate) fn new(consensus_config: ConsensusConfig, quorum_type: QuorumType) -> Self { Self { - validator_id, - sync_retry_interval, + consensus_config, quorum_type, future_votes: BTreeMap::new(), cached_proposals: BTreeMap::new(), - timeouts, } } + /// Apply the full dynamic consensus configuration. Call only between heights. + pub(crate) fn set_dynamic_config(&mut self, cfg: ConsensusDynamicConfig) { + self.consensus_config.dynamic_config = cfg; + } + /// Run the consensus algorithm for a single height. /// /// A height of consensus ends either when the node learns of a decision, either by consensus @@ -244,7 +271,8 @@ impl MultiHeightManager { } let validators = context.validators(height).await; - let is_observer = must_observer || !validators.contains(&self.validator_id); + let is_observer = must_observer + || !validators.contains(&self.consensus_config.dynamic_config.validator_id); info!( "START_HEIGHT: running consensus for height {:?}. is_observer: {}, validators: {:?}", height, is_observer, validators, @@ -253,10 +281,10 @@ impl MultiHeightManager { let mut shc = SingleHeightConsensus::new( height, is_observer, - self.validator_id, + self.consensus_config.dynamic_config.validator_id, validators, self.quorum_type, - self.timeouts.clone(), + self.consensus_config.dynamic_config.timeouts.clone(), ); let mut shc_events = FuturesUnordered::new(); @@ -273,7 +301,8 @@ impl MultiHeightManager { // Loop over incoming proposals, messages, and self generated events. let clock = DefaultClock; - let mut sync_poll_deadline = clock.now() + self.sync_retry_interval; + let sync_retry_interval = self.consensus_config.dynamic_config.sync_retry_interval; + let mut sync_poll_deadline = clock.now() + sync_retry_interval; loop { self.report_max_cached_block_number_metric(height); let shc_return = tokio::select! { @@ -289,8 +318,8 @@ impl MultiHeightManager { }, // Using sleep_until to make sure that we won't restart the sleep due to other // events occuring. - _ = sleep_until(sync_poll_deadline, &clock) => { - sync_poll_deadline += self.sync_retry_interval; + _ = clock.sleep_until(sync_poll_deadline) => { + sync_poll_deadline += sync_retry_interval; if context.try_sync(height).await { return Ok(RunHeightRes::Sync); } diff --git a/crates/apollo_consensus/src/manager_test.rs b/crates/apollo_consensus/src/manager_test.rs index ceeda301207..3b646efbd5a 100644 --- a/crates/apollo_consensus/src/manager_test.rs +++ b/crates/apollo_consensus/src/manager_test.rs @@ -1,19 +1,27 @@ +use std::sync::Arc; use std::time::Duration; use std::vec; -use apollo_consensus_config::config::TimeoutsConfig; +use apollo_config_manager_types::communication::MockConfigManagerClient; +use apollo_consensus_config::config::{ + ConsensusConfig, + ConsensusDynamicConfig, + ConsensusStaticConfig, + TimeoutsConfig, +}; use apollo_network::network_manager::test_utils::{ mock_register_broadcast_topic, MockBroadcastedMessagesSender, TestSubscriberChannels, }; use apollo_network_types::network_types::BroadcastedMessageMetadata; -use apollo_protobuf::consensus::{Vote, DEFAULT_VALIDATOR_ID}; +use apollo_protobuf::consensus::{ProposalCommitment, Vote, DEFAULT_VALIDATOR_ID}; use apollo_test_utils::{get_rng, GetTestInstance}; use futures::channel::{mpsc, oneshot}; use futures::{FutureExt, SinkExt}; use lazy_static::lazy_static; -use starknet_api::block::{BlockHash, BlockNumber}; +use rstest::{fixture, rstest}; +use starknet_api::block::BlockNumber; use starknet_types_core::felt::Felt; use super::{run_consensus, MultiHeightManager, RunHeightRes}; @@ -37,6 +45,18 @@ lazy_static! { const CHANNEL_SIZE: usize = 10; const SYNC_RETRY_INTERVAL: Duration = Duration::from_millis(100); +#[fixture] +fn consensus_config() -> ConsensusConfig { + ConsensusConfig::from_parts( + ConsensusDynamicConfig { + validator_id: *VALIDATOR_ID, + timeouts: TIMEOUTS.clone(), + sync_retry_interval: SYNC_RETRY_INTERVAL, + }, + ConsensusStaticConfig { startup_delay: Duration::ZERO, ..Default::default() }, + ) +} + async fn send(sender: &mut MockBroadcastedMessagesSender, msg: Vote) { let broadcasted_message_metadata = BroadcastedMessageMetadata::get_test_instance(&mut get_rng()); @@ -59,7 +79,7 @@ fn expect_validate_proposal(context: &mut MockTestContext, block_hash: Felt, tim .expect_validate_proposal() .returning(move |_, _, _| { let (block_sender, block_receiver) = oneshot::channel(); - block_sender.send(BlockHash(block_hash)).unwrap(); + block_sender.send(ProposalCommitment(block_hash)).unwrap(); block_receiver }) .times(times); @@ -67,13 +87,14 @@ fn expect_validate_proposal(context: &mut MockTestContext, block_hash: Felt, tim fn assert_decision(res: RunHeightRes, id: Felt) { match res { - RunHeightRes::Decision(decision) => assert_eq!(decision.block, BlockHash(id)), + RunHeightRes::Decision(decision) => assert_eq!(decision.block, ProposalCommitment(id)), _ => panic!("Expected decision"), } } +#[rstest] #[tokio::test] -async fn manager_multiple_heights_unordered() { +async fn manager_multiple_heights_unordered(consensus_config: ConsensusConfig) { let TestSubscriberChannels { mock_network, subscriber_channels } = mock_register_broadcast_topic().unwrap(); let mut sender = mock_network.broadcasted_messages_sender; @@ -107,12 +128,7 @@ async fn manager_multiple_heights_unordered() { context.expect_set_height_and_round().returning(move |_, _| ()); context.expect_broadcast().returning(move |_| Ok(())); - let mut manager = MultiHeightManager::new( - *VALIDATOR_ID, - SYNC_RETRY_INTERVAL, - QuorumType::Byzantine, - TIMEOUTS.clone(), - ); + let mut manager = MultiHeightManager::new(consensus_config, QuorumType::Byzantine); let mut subscriber_channels = subscriber_channels.into(); let decision = manager .run_height( @@ -141,8 +157,9 @@ async fn manager_multiple_heights_unordered() { assert_decision(decision, Felt::TWO); } +#[rstest] #[tokio::test] -async fn run_consensus_sync() { +async fn run_consensus_sync(consensus_config: ConsensusConfig) { // Set expectations. let mut context = MockTestContext::new(); let (decision_tx, decision_rx) = oneshot::channel(); @@ -156,7 +173,7 @@ async fn run_consensus_sync() { context.expect_broadcast().returning(move |_| Ok(())); context .expect_decision_reached() - .withf(move |block, votes| *block == BlockHash(Felt::TWO) && votes[0].height == 2) + .withf(move |block, votes| *block == ProposalCommitment(Felt::TWO) && votes[0].height == 2) .return_once(move |_, _| { decision_tx.send(()).unwrap(); Ok(()) @@ -180,13 +197,11 @@ async fn run_consensus_sync() { send(&mut network_sender, prevote(Some(Felt::TWO), 2, 0, *PROPOSER_ID)).await; send(&mut network_sender, precommit(Some(Felt::TWO), 2, 0, *PROPOSER_ID)).await; let run_consensus_args = RunConsensusArguments { + consensus_config, start_active_height: BlockNumber(1), start_observe_height: BlockNumber(1), - validator_id: *VALIDATOR_ID, - consensus_delay: Duration::ZERO, - timeouts: TIMEOUTS.clone(), - sync_retry_interval: SYNC_RETRY_INTERVAL, quorum_type: QuorumType::Byzantine, + config_manager_client: None, }; // Start at height 1. tokio::spawn(async move { @@ -203,8 +218,9 @@ async fn run_consensus_sync() { decision_rx.await.unwrap(); } +#[rstest] #[tokio::test] -async fn test_timeouts() { +async fn test_timeouts(consensus_config: ConsensusConfig) { let TestSubscriberChannels { mock_network, subscriber_channels } = mock_register_broadcast_topic().unwrap(); let mut sender = mock_network.broadcasted_messages_sender; @@ -243,12 +259,8 @@ async fn test_timeouts() { }); context.expect_broadcast().returning(move |_| Ok(())); - let mut manager = MultiHeightManager::new( - *VALIDATOR_ID, - SYNC_RETRY_INTERVAL, - QuorumType::Byzantine, - TIMEOUTS.clone(), - ); + // Ensure our validator id matches the expectation in the broadcast assertion. + let mut manager = MultiHeightManager::new(consensus_config, QuorumType::Byzantine); let manager_handle = tokio::spawn(async move { let decision = manager .run_height( @@ -281,8 +293,9 @@ async fn test_timeouts() { manager_handle.await.unwrap(); } +#[rstest] #[tokio::test] -async fn timely_message_handling() { +async fn timely_message_handling(consensus_config: ConsensusConfig) { // TODO(matan): Make run_height more generic so don't need mock network? // Check that, even when sync is immediately ready, consensus still handles queued messages. let mut context = MockTestContext::new(); @@ -304,12 +317,7 @@ async fn timely_message_handling() { // Fill up the buffer. while vote_sender.send((vote.clone(), metadata.clone())).now_or_never().is_some() {} - let mut manager = MultiHeightManager::new( - *VALIDATOR_ID, - SYNC_RETRY_INTERVAL, - QuorumType::Byzantine, - TIMEOUTS.clone(), - ); + let mut manager = MultiHeightManager::new(consensus_config, QuorumType::Byzantine); let res = manager .run_height( &mut context, @@ -326,3 +334,79 @@ async fn timely_message_handling() { proposal_receiver_sender.try_send(mpsc::channel(1).1).unwrap(); assert!(vote_sender.send((vote.clone(), metadata.clone())).now_or_never().is_some()); } + +#[rstest] +#[tokio::test] +async fn run_consensus_dynamic_client_updates_validator_between_heights( + consensus_config: ConsensusConfig, +) { + let TestSubscriberChannels { mock_network, subscriber_channels } = + mock_register_broadcast_topic().unwrap(); + // Keep a handle to the vote sender so the paired receiver stays alive. + let _vote_sender = mock_network.broadcasted_messages_sender; + let (_proposal_receiver_sender, proposal_receiver_receiver) = mpsc::channel(CHANNEL_SIZE); + + // Context with expectations: H1 we are the validator, learn height via sync; at H2 we are the + // proposer. + let mut context = MockTestContext::new(); + context.expect_set_height_and_round().returning(move |_, _| ()); + context.expect_validators().returning(move |h: BlockNumber| { + if h == BlockNumber(1) { vec![*VALIDATOR_ID] } else { vec![*PROPOSER_ID] } + }); + context.expect_proposer().returning(move |h: BlockNumber, _| { + if h == BlockNumber(1) { *VALIDATOR_ID } else { *PROPOSER_ID } + }); + context.expect_try_sync().withf(move |h| *h == BlockNumber(1)).times(1).returning(|_| true); + context.expect_try_sync().returning(|_| false); + context.expect_broadcast().returning(move |_| Ok(())); + + // In this test, build_proposal should be called only when the dynamic config returns that we + // are the proposer, which happens at H2. + context + .expect_build_proposal() + .withf(move |init, _| init.height == BlockNumber(2) && init.proposer == *PROPOSER_ID) + .returning(move |_, _| { + let (sender, receiver) = oneshot::channel(); + sender.send(ProposalCommitment(Felt::TWO)).unwrap(); + receiver + }) + .times(1); + // Expect a decision at height 2. + let (decision_tx, decision_rx) = oneshot::channel(); + context + .expect_decision_reached() + .withf(move |_, votes| votes.first().map(|v| v.height) == Some(2)) + .return_once(move |_, _| { + let _ = decision_tx.send(()); + Ok(()) + }) + .times(1); + + // Dynamic client mock: H1 -> VALIDATOR_ID, H2 -> PROPOSER_ID (order is important) + let mut mock_client = MockConfigManagerClient::new(); + let validator_config = consensus_config.dynamic_config.clone(); + let proposer_config = + ConsensusDynamicConfig { validator_id: *PROPOSER_ID, ..validator_config.clone() }; + mock_client.expect_get_consensus_dynamic_config().times(1).return_const(Ok(validator_config)); + mock_client.expect_get_consensus_dynamic_config().times(1).return_const(Ok(proposer_config)); + + let run_consensus_args = RunConsensusArguments { + start_active_height: BlockNumber(1), + start_observe_height: BlockNumber(1), + consensus_config, + quorum_type: QuorumType::Byzantine, + config_manager_client: Some(Arc::new(mock_client)), + }; + + // Spawn consensus and wait for a decision at height 2. + tokio::spawn(async move { + run_consensus( + run_consensus_args, + context, + subscriber_channels.into(), + proposal_receiver_receiver, + ) + .await + }); + decision_rx.await.unwrap(); +} diff --git a/crates/apollo_consensus/src/metrics.rs b/crates/apollo_consensus/src/metrics.rs index 635b0afab52..1484f9d84fb 100644 --- a/crates/apollo_consensus/src/metrics.rs +++ b/crates/apollo_consensus/src/metrics.rs @@ -10,6 +10,7 @@ define_metrics!( MetricGauge { CONSENSUS_CACHED_VOTES, "consensus_cached_votes", "How many votes are cached when starting to work on a new block number" }, MetricCounter { CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS, "consensus_decisions_reached_by_consensus", "The total number of decisions reached by way of consensus", init=0}, MetricCounter { CONSENSUS_DECISIONS_REACHED_BY_SYNC, "consensus_decisions_reached_by_sync", "The total number of decisions reached by way of sync", init=0}, + MetricCounter { CONSENSUS_DECISIONS_REACHED_AS_PROPOSER, "consensus_decisions_reached_as_proposer", "The total number of rounds with decision reached where this node is the proposer", init=0}, MetricCounter { CONSENSUS_PROPOSALS_RECEIVED, "consensus_proposals_received", "The total number of proposals received", init=0}, MetricCounter { CONSENSUS_PROPOSALS_VALID_INIT, "consensus_proposals_valid_init", "The total number of proposals received with a valid init", init=0}, MetricCounter { CONSENSUS_PROPOSALS_VALIDATED, "consensus_proposals_validated", "The total number of complete, valid proposals received", init=0}, @@ -26,6 +27,7 @@ define_metrics!( MetricCounter { CONSENSUS_INBOUND_STREAM_FINISHED, "consensus_inbound_stream_finished", "The total number of inbound streams finished", init=0 }, // TODO(Matan): remove this metric. MetricCounter { CONSENSUS_ROUND_ABOVE_ZERO, "consensus_round_above_zero", "The number of times the consensus round has increased above zero", init=0 }, + MetricCounter { CONSENSUS_ROUND_ADVANCES, "consensus_round_advances", "The number of times the consensus round has advanced", init=0 }, MetricCounter { CONSENSUS_CONFLICTING_VOTES, "consensus_conflicting_votes", "The number of times consensus has received conflicting votes", init=0 }, LabeledMetricCounter { CONSENSUS_TIMEOUTS, "consensus_timeouts", "The number of times consensus has timed out", init=0, labels = CONSENSUS_TIMEOUT_LABELS }, }, @@ -53,6 +55,7 @@ pub(crate) fn register_metrics() { CONSENSUS_CACHED_VOTES.register(); CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS.register(); CONSENSUS_DECISIONS_REACHED_BY_SYNC.register(); + CONSENSUS_DECISIONS_REACHED_AS_PROPOSER.register(); CONSENSUS_PROPOSALS_RECEIVED.register(); CONSENSUS_PROPOSALS_VALID_INIT.register(); CONSENSUS_PROPOSALS_VALIDATED.register(); @@ -68,6 +71,7 @@ pub(crate) fn register_metrics() { CONSENSUS_OUTBOUND_STREAM_STARTED.register(); CONSENSUS_OUTBOUND_STREAM_FINISHED.register(); CONSENSUS_ROUND_ABOVE_ZERO.register(); + CONSENSUS_ROUND_ADVANCES.register(); CONSENSUS_CONFLICTING_VOTES.register(); CONSENSUS_TIMEOUTS.register(); } diff --git a/crates/apollo_consensus/src/single_height_consensus.rs b/crates/apollo_consensus/src/single_height_consensus.rs index adbd651aa44..6f11e9e96f3 100644 --- a/crates/apollo_consensus/src/single_height_consensus.rs +++ b/crates/apollo_consensus/src/single_height_consensus.rs @@ -295,10 +295,10 @@ impl SingleHeightConsensus { // this round. While this prevents spam attacks it also prevents re-receiving after // a network issue. let old = self.proposals.insert(round, proposal_id); - let old = old.unwrap_or_else(|| { - panic!("Proposal entry should exist from init. round={round}") - }); - assert!(old.is_none(), "Proposal already exists for this round={round}. {old:?}"); + assert!( + old.is_some_and(|p| p.is_none()), + "Proposal entry for round {round} should exist and be empty: {old:?}" + ); let sm_events = self.state_machine.handle_event( StateMachineEvent::Proposal(proposal_id, round, valid_round), &leader_fn, @@ -354,12 +354,14 @@ impl SingleHeightConsensus { } let (votes, sm_vote) = match vote.vote_type { - VoteType::Prevote => { - (&mut self.prevotes, StateMachineEvent::Prevote(vote.block_hash, vote.round)) - } - VoteType::Precommit => { - (&mut self.precommits, StateMachineEvent::Precommit(vote.block_hash, vote.round)) - } + VoteType::Prevote => ( + &mut self.prevotes, + StateMachineEvent::Prevote(vote.proposal_commitment, vote.round), + ), + VoteType::Precommit => ( + &mut self.precommits, + StateMachineEvent::Precommit(vote.proposal_commitment, vote.round), + ), }; match votes.entry((vote.round, vote.voter)) { @@ -368,7 +370,7 @@ impl SingleHeightConsensus { } Entry::Occupied(entry) => { let old = entry.get(); - if old.block_hash != vote.block_hash { + if old.proposal_commitment != vote.proposal_commitment { warn!("Conflicting votes: old={:?}, new={:?}", old, vote); CONSENSUS_CONFLICTING_VOTES.increment(1); return Ok(ShcReturn::Tasks(Vec::new())); @@ -479,14 +481,15 @@ impl SingleHeightConsensus { }; let proposal_id = proposal_id.expect("Reproposal must have a valid ID"); - let id = self - .proposals - .get(&valid_round) - .unwrap_or_else(|| panic!("A proposal should exist for valid_round: {valid_round}")) - .unwrap_or_else(|| { - panic!("A valid proposal should exist for valid_round: {valid_round}") - }); - assert_eq!(id, proposal_id, "reproposal should match the stored proposal"); + // Make sure there is an existing proposal for the valid round and it matches the proposal + // ID. + let existing = self.proposals.get(&valid_round).and_then(|&inner| inner); + assert!( + existing.is_some_and(|id| id == proposal_id), + "A proposal with ID {proposal_id:?} should exist for valid_round: {valid_round}. \ + Found: {existing:?}", + ); + let old = self.proposals.insert(round, Some(proposal_id)); assert!(old.is_none(), "There should be no proposal for round {round}."); let init = ProposalInit { @@ -496,7 +499,7 @@ impl SingleHeightConsensus { valid_round: Some(valid_round), }; CONSENSUS_REPROPOSALS.increment(1); - context.repropose(id, init).await; + context.repropose(proposal_id, init).await; } async fn handle_state_machine_vote( @@ -528,7 +531,7 @@ impl SingleHeightConsensus { vote_type, height: self.height.0, round, - block_hash: proposal_id, + proposal_commitment: proposal_id, voter: self.id, }; if let Some(old) = votes.insert((round, self.id), vote.clone()) { @@ -576,7 +579,8 @@ impl SingleHeightConsensus { })?; if block != proposal_id { return Err(invalid_decision(format!( - "StateMachine block hash should match the stored block. Shc.block_id: {block}" + "StateMachine proposal commitment should match the stored block. Shc.block_id: \ + {block}" ))); } let supporting_precommits: Vec = self @@ -584,7 +588,11 @@ impl SingleHeightConsensus { .iter() .filter_map(|v| { let vote = self.precommits.get(&(round, *v))?; - if vote.block_hash == Some(proposal_id) { Some(vote.clone()) } else { None } + if vote.proposal_commitment == Some(proposal_id) { + Some(vote.clone()) + } else { + None + } }) .collect(); diff --git a/crates/apollo_consensus/src/single_height_consensus_test.rs b/crates/apollo_consensus/src/single_height_consensus_test.rs index 3867eff35ce..ba41aa43a25 100644 --- a/crates/apollo_consensus/src/single_height_consensus_test.rs +++ b/crates/apollo_consensus/src/single_height_consensus_test.rs @@ -3,7 +3,7 @@ use apollo_protobuf::consensus::{ProposalFin, ProposalInit, Vote, DEFAULT_VALIDA use futures::channel::{mpsc, oneshot}; use futures::SinkExt; use lazy_static::lazy_static; -use starknet_api::block::{BlockHash, BlockNumber}; +use starknet_api::block::BlockNumber; use starknet_types_core::felt::Felt; use test_case::test_case; @@ -11,7 +11,7 @@ use super::SingleHeightConsensus; use crate::single_height_consensus::{ShcReturn, ShcTask}; use crate::state_machine::StateMachineEvent; use crate::test_utils::{precommit, prevote, MockTestContext, TestBlock, TestProposalPart}; -use crate::types::ValidatorId; +use crate::types::{ProposalCommitment, ValidatorId}; use crate::votes_threshold::QuorumType; lazy_static! { @@ -21,7 +21,8 @@ lazy_static! { static ref VALIDATOR_ID_3: ValidatorId = (DEFAULT_VALIDATOR_ID + 3).into(); static ref VALIDATORS: Vec = vec![*PROPOSER_ID, *VALIDATOR_ID_1, *VALIDATOR_ID_2, *VALIDATOR_ID_3]; - static ref BLOCK: TestBlock = TestBlock { content: vec![1, 2, 3], id: BlockHash(Felt::ONE) }; + static ref BLOCK: TestBlock = + TestBlock { content: vec![1, 2, 3], id: ProposalCommitment(Felt::ONE) }; static ref PROPOSAL_INIT: ProposalInit = ProposalInit { proposer: *PROPOSER_ID, ..Default::default() }; static ref TIMEOUTS: TimeoutsConfig = TimeoutsConfig::default(); @@ -38,14 +39,14 @@ const CHANNEL_SIZE: usize = 1; fn prevote_task(block_felt: Option, round: u32) -> ShcTask { ShcTask::Prevote( TIMEOUTS.prevote_timeout, - StateMachineEvent::Prevote(block_felt.map(BlockHash), round), + StateMachineEvent::Prevote(block_felt.map(ProposalCommitment), round), ) } fn precommit_task(block_felt: Option, round: u32) -> ShcTask { ShcTask::Precommit( TIMEOUTS.precommit_timeout, - StateMachineEvent::Precommit(block_felt.map(BlockHash), round), + StateMachineEvent::Precommit(block_felt.map(ProposalCommitment), round), ) } diff --git a/crates/apollo_consensus/src/state_machine.rs b/crates/apollo_consensus/src/state_machine.rs index 791cf63a65c..ff7ae538851 100644 --- a/crates/apollo_consensus/src/state_machine.rs +++ b/crates/apollo_consensus/src/state_machine.rs @@ -18,6 +18,7 @@ use crate::metrics::{ CONSENSUS_NEW_VALUE_LOCKS, CONSENSUS_ROUND, CONSENSUS_ROUND_ABOVE_ZERO, + CONSENSUS_ROUND_ADVANCES, CONSENSUS_TIMEOUTS, LABEL_NAME_TIMEOUT_TYPE, }; @@ -204,7 +205,7 @@ impl StateMachine { return output_events; } StateMachineEvent::GetProposal(_, _) => { - // LOC 18. + // LOC 18 in the paper. assert!(resultant_events.is_empty()); assert!(!self.is_observer); output_events.push_back(e); @@ -370,9 +371,12 @@ impl StateMachine { LeaderFn: Fn(Round) -> ValidatorId, { CONSENSUS_ROUND.set(round); - // Count how many times consensus advanced above round 0. - if round == 1 { - CONSENSUS_ROUND_ABOVE_ZERO.increment(1); + if round > 0 { + CONSENSUS_ROUND_ADVANCES.increment(1); + // Count how many times consensus advanced above round 0. + if round == 1 { + CONSENSUS_ROUND_ABOVE_ZERO.increment(1); + } } if self.locked_value_round.is_some() { CONSENSUS_HELD_LOCKS.increment(1); diff --git a/crates/apollo_consensus/src/state_machine_test.rs b/crates/apollo_consensus/src/state_machine_test.rs index 44a1888dfbc..3a4cb34f609 100644 --- a/crates/apollo_consensus/src/state_machine_test.rs +++ b/crates/apollo_consensus/src/state_machine_test.rs @@ -2,7 +2,6 @@ use std::collections::VecDeque; use apollo_protobuf::consensus::DEFAULT_VALIDATOR_ID; use lazy_static::lazy_static; -use starknet_api::block::BlockHash; use starknet_types_core::felt::Felt; use test_case::test_case; @@ -16,7 +15,7 @@ lazy_static! { static ref VALIDATOR_ID: ValidatorId = (DEFAULT_VALIDATOR_ID + 1).into(); } -const PROPOSAL_ID: Option = Some(BlockHash(Felt::ONE)); +const PROPOSAL_ID: Option = Some(ProposalCommitment(Felt::ONE)); const ROUND: Round = 0; struct TestWrapper ValidatorId> { diff --git a/crates/apollo_consensus/src/stream_handler.rs b/crates/apollo_consensus/src/stream_handler.rs index 37f2489dacc..9df303943e6 100644 --- a/crates/apollo_consensus/src/stream_handler.rs +++ b/crates/apollo_consensus/src/stream_handler.rs @@ -143,7 +143,7 @@ where // (stream_id, Receiver) pair. Each receiver gets messages that should // be sent out to the network. outbound_channel_receiver: mpsc::Receiver<(StreamId, mpsc::Receiver)>, - // A map where the abovementioned Receivers are stored. + // A map where the above mentioned Receivers are stored. outbound_stream_receivers: StreamMap>, // A network sender that allows sending StreamMessages to peers. outbound_sender: OutboundSenderT, @@ -198,6 +198,8 @@ where /// /// Expects to live forever, returning an Error if the client or network close their sender. pub async fn handle_next_msg(&mut self) -> Result<(), StreamHandlerError> { + // TODO(Dafna): Consider spawning a separate task for each of the three channels to ensure + // they don’t block one another. tokio::select!( // New outbound stream. outbound_stream = self.outbound_channel_receiver.next() => { @@ -245,48 +247,53 @@ where } } + // Returns true if the message was successfully sent. + // If the message was not sent, it is either due to disconnected channel or full channel. fn inbound_send( &mut self, data: &mut StreamData, message: StreamMessage, ) -> bool { - // TODO(guyn): reconsider the "expect" here. + let content = match message.message { + StreamMessageBody::Content(content) => content, + // A Fin message is not sent. This is a no-op, can safely return false. + StreamMessageBody::Fin => return false, + }; + let sender = &mut data.sender; - if let StreamMessageBody::Content(content) = message.message { - match sender.try_send(content) { - Ok(_) => {} - Err(e) => { - if e.is_disconnected() { - warn!( - "Sender is disconnected, dropping the message. StreamId: {}, \ - MessageId: {}", - message.stream_id, message.message_id - ); - return true; - } else if e.is_full() { - // TODO(guyn): replace panic with buffering of the message. - panic!( - "Sender is full, dropping the message. StreamId: {}, MessageId: {}", - message.stream_id, message.message_id - ); - } else { - // TODO(guyn): replace panic with more graceful error handling - panic!("Unexpected error: {e:?}"); - } + if let Err(e) = sender.try_send(content) { + warn!( + "Error sending inbound message: {e:?}; dropping the message. StreamId: {}, \ + MessageId: {}", + message.stream_id, message.message_id + ); + return false; + } + + // Send the receiver only once the first message has been sent. + if message.message_id == 0 { + // If this is the first message, send the receiver to the application. + // Note: By this point, messages must be unique. Duplicate message IDs should + // have been discarded earlier. + let receiver = data.receiver.take().expect("Receiver should exist"); + + // Send the receiver to the application. + let send_result = self.inbound_channel_sender.try_send(receiver); + if let Err(e) = send_result { + if e.is_disconnected() { + panic!("Receiver was unexpectedly dropped"); + } else { + // The channel is full. + warn!( + "Failed to send receiver to application: {e:?}; dropping the message. \ + StreamId: {}, MessageId: {}", + message.stream_id, message.message_id + ); + return false; } - }; - // Send the receiver only once the first message has been sent. - if message.message_id == 0 { - // TODO(guyn): consider the expect in both cases. - // If this is the first message, send the receiver to the application. - let receiver = data.receiver.take().expect("Receiver should exist"); - // Send the receiver to the application. - self.inbound_channel_sender.try_send(receiver).expect("Send should succeed"); } - data.next_message_id += 1; - return false; } - // A Fin message is not sent. This is a no-op, can safely return true. + data.next_message_id += 1; true } @@ -300,7 +307,6 @@ where stream_id: stream_id.clone(), message_id: *self.outbound_stream_number.get(&stream_id).unwrap_or(&0), }; - // TODO(guyn): reconsider the "expect" here. self.outbound_sender.broadcast_message(message).await.expect("Send should succeed"); self.outbound_stream_number.insert( stream_id.clone(), @@ -414,13 +420,19 @@ where // This means we can just send the message without buffering it. match message_id.cmp(&data.next_message_id) { Ordering::Equal => { - let mut receiver_dropped = self.inbound_send(&mut data, message); - if !receiver_dropped { - receiver_dropped = self.process_buffer(&mut data); + let mut message_sent = self.inbound_send(&mut data, message); + if message_sent { + message_sent = self.process_buffer(&mut data); } - if data.message_buffer.is_empty() && data.fin_message_id.is_some() - || receiver_dropped + // We are done with this stream if: + // 1. All messages were sent successfully. + // 2. A send error occurred (receiver dropped or channel full). + // + // A full channel is currently treated as an error, as we expect + // capacity to always be sufficient. + // TODO(Dafna): Consider implementing buffering as a fallback for the 'full' case. + if data.message_buffer.is_empty() && data.fin_message_id.is_some() || !message_sent { data.sender.close_channel(); CONSENSUS_INBOUND_STREAM_FINISHED.increment(1); @@ -469,14 +481,15 @@ where } // Tries to drain as many messages as possible from the buffer (in order), - // DOES NOT guarantee that the buffer will be empty after calling this function. - // Returns true if the receiver for this stream is dropped. + // Returns true if all attempted messages were sent successfully, and false otherwise. + // DOES NOT guarantee that the buffer will be empty after calling this function - only that the + // messages which were tried to send were handled successfully. fn process_buffer(&mut self, data: &mut StreamData) -> bool { while let Some(message) = data.message_buffer.remove(&data.next_message_id) { - if self.inbound_send(data, message) { - return true; + if !self.inbound_send(data, message) { + return false; } } - false + true } } diff --git a/crates/apollo_consensus/src/test_utils.rs b/crates/apollo_consensus/src/test_utils.rs index 6a5447cf3bd..01a62155b6c 100644 --- a/crates/apollo_consensus/src/test_utils.rs +++ b/crates/apollo_consensus/src/test_utils.rs @@ -1,20 +1,20 @@ use std::time::Duration; -use apollo_protobuf::consensus::{ProposalInit, Vote, VoteType}; +use apollo_protobuf::consensus::{ProposalCommitment, ProposalInit, Vote, VoteType}; use apollo_protobuf::converters::ProtobufConversionError; use async_trait::async_trait; use futures::channel::{mpsc, oneshot}; use mockall::mock; -use starknet_api::block::{BlockHash, BlockNumber}; +use starknet_api::block::BlockNumber; use starknet_types_core::felt::Felt; -use crate::types::{ConsensusContext, ConsensusError, ProposalCommitment, Round, ValidatorId}; +use crate::types::{ConsensusContext, ConsensusError, Round, ValidatorId}; /// Define a consensus block which can be used to enable auto mocking Context. #[derive(Debug, PartialEq, Clone)] pub struct TestBlock { pub content: Vec, - pub id: BlockHash, + pub id: ProposalCommitment, } #[derive(Debug, PartialEq, Clone)] @@ -97,13 +97,13 @@ mock! { } pub fn prevote(block_felt: Option, height: u64, round: u32, voter: ValidatorId) -> Vote { - let block_hash = block_felt.map(BlockHash); - Vote { vote_type: VoteType::Prevote, height, round, block_hash, voter } + let proposal_commitment = block_felt.map(ProposalCommitment); + Vote { vote_type: VoteType::Prevote, height, round, proposal_commitment, voter } } pub fn precommit(block_felt: Option, height: u64, round: u32, voter: ValidatorId) -> Vote { - let block_hash = block_felt.map(BlockHash); - Vote { vote_type: VoteType::Precommit, height, round, block_hash, voter } + let proposal_commitment = block_felt.map(ProposalCommitment); + Vote { vote_type: VoteType::Precommit, height, round, proposal_commitment, voter } } pub fn proposal_init(height: u64, round: u32, proposer: ValidatorId) -> ProposalInit { ProposalInit { height: BlockNumber(height), round, proposer, ..Default::default() } diff --git a/crates/apollo_consensus/src/types.rs b/crates/apollo_consensus/src/types.rs index afca6e34327..07454929dbc 100644 --- a/crates/apollo_consensus/src/types.rs +++ b/crates/apollo_consensus/src/types.rs @@ -8,11 +8,12 @@ use apollo_network::network_manager::{ GenericReceiver, }; use apollo_network_types::network_types::BroadcastedMessageMetadata; +pub use apollo_protobuf::consensus::ProposalCommitment; use apollo_protobuf::consensus::{ProposalInit, Vote}; use apollo_protobuf::converters::ProtobufConversionError; use async_trait::async_trait; use futures::channel::{mpsc, oneshot}; -use starknet_api::block::{BlockHash, BlockNumber}; +use starknet_api::block::BlockNumber; use starknet_api::core::ContractAddress; /// Used to identify the node by consensus. @@ -22,7 +23,6 @@ use starknet_api::core::ContractAddress; // TODO(matan): Determine the actual type of NodeId. pub type ValidatorId = ContractAddress; pub type Round = u32; -pub type ProposalCommitment = BlockHash; /// Interface for consensus to call out to the node. /// diff --git a/crates/apollo_consensus_config/src/config.rs b/crates/apollo_consensus_config/src/config.rs index 6f31cf000c3..64bef0999ac 100644 --- a/crates/apollo_consensus_config/src/config.rs +++ b/crates/apollo_consensus_config/src/config.rs @@ -22,6 +22,11 @@ use crate::ValidatorId; pub struct ConsensusDynamicConfig { /// The validator ID of the node. pub validator_id: ValidatorId, + /// Timeouts configuration for consensus. + pub timeouts: TimeoutsConfig, + /// The duration (seconds) between sync attempts. + #[serde(deserialize_with = "deserialize_float_seconds_to_duration")] + pub sync_retry_interval: Duration, } /// Static configuration for consensus that doesn't change during runtime. @@ -30,11 +35,6 @@ pub struct ConsensusStaticConfig { /// The delay (seconds) before starting consensus to give time for network peering. #[serde(deserialize_with = "deserialize_seconds_to_duration")] pub startup_delay: Duration, - /// Timeouts configuration for consensus. - pub timeouts: TimeoutsConfig, - /// The duration (seconds) between sync attempts. - #[serde(deserialize_with = "deserialize_float_seconds_to_duration")] - pub sync_retry_interval: Duration, /// How many heights in the future should we cache. pub future_height_limit: u32, /// How many rounds in the future (for current height) should we cache. @@ -54,30 +54,34 @@ pub struct ConsensusConfig { impl SerializeConfig for ConsensusDynamicConfig { fn dump(&self) -> BTreeMap { - BTreeMap::from_iter([ser_param( - "validator_id", - &self.validator_id, - "The validator id of the node.", - ParamPrivacyInput::Public, - )]) + let mut config = BTreeMap::from_iter([ + ser_param( + "validator_id", + &self.validator_id, + "The validator id of the node.", + ParamPrivacyInput::Public, + ), + ser_param( + "sync_retry_interval", + &self.sync_retry_interval.as_secs_f64(), + "The duration (seconds) between sync attempts.", + ParamPrivacyInput::Public, + ), + ]); + config.extend(prepend_sub_config_name(self.timeouts.dump(), "timeouts")); + config } } impl SerializeConfig for ConsensusStaticConfig { fn dump(&self) -> BTreeMap { - let mut config = BTreeMap::from_iter([ + BTreeMap::from_iter([ ser_param( "startup_delay", &self.startup_delay.as_secs(), "Delay (seconds) before starting consensus to give time for network peering.", ParamPrivacyInput::Public, ), - ser_param( - "sync_retry_interval", - &self.sync_retry_interval.as_secs_f64(), - "The duration (seconds) between sync attempts.", - ParamPrivacyInput::Public, - ), ser_param( "future_height_limit", &self.future_height_limit, @@ -96,9 +100,7 @@ impl SerializeConfig for ConsensusStaticConfig { "How many rounds should we cache for future heights.", ParamPrivacyInput::Public, ), - ]); - config.extend(prepend_sub_config_name(self.timeouts.dump(), "timeouts")); - config + ]) } } @@ -113,7 +115,11 @@ impl SerializeConfig for ConsensusConfig { impl Default for ConsensusDynamicConfig { fn default() -> Self { - Self { validator_id: ValidatorId::from(DEFAULT_VALIDATOR_ID) } + Self { + validator_id: ValidatorId::from(DEFAULT_VALIDATOR_ID), + timeouts: TimeoutsConfig::default(), + sync_retry_interval: Duration::from_secs_f64(1.0), + } } } @@ -121,8 +127,6 @@ impl Default for ConsensusStaticConfig { fn default() -> Self { Self { startup_delay: Duration::from_secs(5), - timeouts: TimeoutsConfig::default(), - sync_retry_interval: Duration::from_secs_f64(1.0), future_height_limit: 10, future_round_limit: 10, future_height_round_limit: 1, diff --git a/crates/apollo_consensus_manager/src/consensus_manager.rs b/crates/apollo_consensus_manager/src/consensus_manager.rs index 2943c43bb2c..479c965c52a 100644 --- a/crates/apollo_consensus_manager/src/consensus_manager.rs +++ b/crates/apollo_consensus_manager/src/consensus_manager.rs @@ -11,7 +11,6 @@ use apollo_class_manager_types::transaction_converter::TransactionConverter; use apollo_class_manager_types::SharedClassManagerClient; use apollo_config_manager_types::communication::SharedConfigManagerClient; use apollo_consensus::stream_handler::StreamHandler; -use apollo_consensus::types::ConsensusError; use apollo_consensus::votes_threshold::QuorumType; use apollo_consensus_manager_config::config::ConsensusManagerConfig; use apollo_consensus_orchestrator::cende::CendeAmbassador; @@ -28,9 +27,14 @@ use apollo_network::network_manager::metrics::{ EventMetrics, NetworkMetrics, }; -use apollo_network::network_manager::{BroadcastTopicChannels, NetworkManager}; +use apollo_network::network_manager::{ + BroadcastTopicChannels, + BroadcastTopicClient, + BroadcastTopicServer, + NetworkManager, +}; use apollo_protobuf::consensus::{HeightAndRound, ProposalPart, StreamMessage, Vote}; -use apollo_reverts::revert_blocks_and_eternal_pending; +use apollo_reverts::{revert_blocks_and_eternal_pending, RevertComponentData}; use apollo_signature_manager_types::SharedSignatureManagerClient; use apollo_state_sync_types::communication::SharedStateSyncClient; use apollo_time::time::DefaultClock; @@ -40,17 +44,27 @@ use starknet_api::block::BlockNumber; use tracing::{info, info_span, Instrument}; use crate::metrics::{ + register_metrics, CONSENSUS_NETWORK_EVENTS, CONSENSUS_NUM_BLACKLISTED_PEERS, CONSENSUS_NUM_CONNECTED_PEERS, CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES, CONSENSUS_PROPOSALS_NUM_RECEIVED_MESSAGES, CONSENSUS_PROPOSALS_NUM_SENT_MESSAGES, + CONSENSUS_REVERTED_BATCHER_UP_TO_AND_INCLUDING, CONSENSUS_VOTES_NUM_DROPPED_MESSAGES, CONSENSUS_VOTES_NUM_RECEIVED_MESSAGES, CONSENSUS_VOTES_NUM_SENT_MESSAGES, }; +type ProposalStreamMessage = StreamMessage; +type ProposalStreamHandler = StreamHandler< + ProposalPart, + HeightAndRound, + BroadcastTopicServer, + BroadcastTopicClient, +>; + #[derive(Clone)] pub struct ConsensusManager { pub config: ConsensusManagerConfig, @@ -83,11 +97,57 @@ impl ConsensusManager { } } - pub async fn run(&self) -> Result<(), ConsensusError> { + pub async fn run(&self) { if self.config.revert_config.should_revert { self.revert_batcher_blocks(self.config.revert_config.revert_up_to_and_including).await; } + let mut network_manager = self.create_network_manager(); + let (proposals_broadcast_channels, votes_broadcast_channels) = + self.register_broadcast_topics(&mut network_manager); + + let (inbound_internal_sender, inbound_internal_receiver) = + mpsc::channel(self.config.stream_handler_config.channel_buffer_capacity); + let (outbound_internal_sender, outbound_internal_receiver) = + mpsc::channel(self.config.stream_handler_config.channel_buffer_capacity); + + let stream_handler = self.create_stream_handler( + proposals_broadcast_channels, + inbound_internal_sender, + outbound_internal_receiver, + ); + + let consensus_context = self.create_sequencer_consensus_context( + &votes_broadcast_channels, + outbound_internal_sender, + ); + + let current_height = + self.batcher_client.get_height().await.expect("Failed to get height from batcher"); + let run_consensus_args = self.create_run_consensus_args(current_height.height); + + let network_task = + tokio::spawn(network_manager.run().instrument(info_span!("[Consensus network]"))); + let stream_handler_task = tokio::spawn(stream_handler.run()); + + let consensus_fut = apollo_consensus::run_consensus( + run_consensus_args, + consensus_context, + votes_broadcast_channels.into(), + inbound_internal_receiver, + ); + + tokio::select! { + consensus_result = consensus_fut => + panic!("Consensus task finished unexpectedly: {:?}", consensus_result), + network_result = network_task => + panic!("Consensus' network task finished unexpectedly: {:?}", network_result), + stream_handler_result = stream_handler_task => + panic!("Consensus' stream handler task finished unexpectedly: {:?}", stream_handler_result) + } + } + + fn create_network_manager(&self) -> NetworkManager { let mut broadcast_metrics_by_topic = HashMap::new(); broadcast_metrics_by_topic.insert( Topic::new(self.config.votes_topic.clone()).hash(), @@ -112,11 +172,36 @@ impl ConsensusManager { sqmr_metrics: None, event_metrics: Some(EventMetrics { event_counter: CONSENSUS_NETWORK_EVENTS }), }); - let mut network_manager = - NetworkManager::new(self.config.network_config.clone(), None, network_manager_metrics); + NetworkManager::new(self.config.network_config.clone(), None, network_manager_metrics) + } + + fn create_stream_handler( + &self, + proposals_broadcast_channels: BroadcastTopicChannels, + inbound_internal_sender: mpsc::Sender>, + outbound_internal_receiver: mpsc::Receiver<(HeightAndRound, mpsc::Receiver)>, + ) -> ProposalStreamHandler { + let BroadcastTopicChannels { + broadcasted_messages_receiver: inbound_network_receiver, + broadcast_topic_client: outbound_network_sender, + } = proposals_broadcast_channels; + + StreamHandler::new( + self.config.stream_handler_config.clone(), + inbound_internal_sender, + inbound_network_receiver, + outbound_internal_receiver, + outbound_network_sender, + ) + } + + fn register_broadcast_topics( + &self, + network_manager: &mut NetworkManager, + ) -> (BroadcastTopicChannels, BroadcastTopicChannels) { let proposals_broadcast_channels = network_manager - .register_broadcast_topic::>( + .register_broadcast_topic::( Topic::new(self.config.proposals_topic.clone()), self.config.broadcast_buffer_size, ) @@ -129,40 +214,15 @@ impl ConsensusManager { ) .expect("Failed to register broadcast topic"); - let BroadcastTopicChannels { - broadcasted_messages_receiver: inbound_network_receiver, - broadcast_topic_client: outbound_network_sender, - } = proposals_broadcast_channels; - - let (inbound_internal_sender, inbound_internal_receiver) = - mpsc::channel(self.config.stream_handler_config.channel_buffer_capacity); - let (outbound_internal_sender, outbound_internal_receiver) = - mpsc::channel(self.config.stream_handler_config.channel_buffer_capacity); - let stream_handler = StreamHandler::new( - self.config.stream_handler_config.clone(), - inbound_internal_sender, - inbound_network_receiver, - outbound_internal_receiver, - outbound_network_sender, - ); - - let observer_height = self - .batcher_client - .get_height() - .await - .expect("Failed to get observer_height from batcher") - .height; - let active_height = if self.config.immediate_active_height == observer_height { - // Setting `start_height` is only used to enable consensus starting immediately without - // observing the first height. This means consensus may return to a height - // it has already voted on, risking equivocation. This is only safe to do if we - // restart all nodes at this height. - observer_height - } else { - BlockNumber(observer_height.0 + 1) - }; + (proposals_broadcast_channels, votes_broadcast_channels) + } - let context = SequencerConsensusContext::new( + fn create_sequencer_consensus_context( + &self, + votes_broadcast_channels: &BroadcastTopicChannels, + outbound_internal_sender: mpsc::Sender<(HeightAndRound, mpsc::Receiver)>, + ) -> SequencerConsensusContext { + SequencerConsensusContext::new( self.config.context_config.clone(), SequencerConsensusContextDeps { transaction_converter: Arc::new(TransactionConverter::new( @@ -180,49 +240,34 @@ impl ConsensusManager { outbound_proposal_sender: outbound_internal_sender, vote_broadcast_client: votes_broadcast_channels.broadcast_topic_client.clone(), }, - ); + ) + } - let network_task = - tokio::spawn(network_manager.run().instrument(info_span!("[Consensus network]"))); - let stream_handler_task = tokio::spawn(stream_handler.run()); + fn create_run_consensus_args( + &self, + current_height: BlockNumber, + ) -> apollo_consensus::RunConsensusArguments { + let observer_height = current_height; + let active_height = if self.config.immediate_active_height == observer_height { + // Setting `start_height` is only used to enable consensus starting immediately without + // observing the first height. This means consensus may return to a height + // it has already voted on, risking equivocation. This is only safe to do if we + // restart all nodes at this height. + observer_height + } else { + BlockNumber(observer_height.0 + 1) + }; let quorum_type = if self.config.assume_no_malicious_validators { QuorumType::Honest } else { QuorumType::Byzantine }; - let run_consensus_args = apollo_consensus::RunConsensusArguments { + apollo_consensus::RunConsensusArguments { + consensus_config: self.config.consensus_manager_config.clone(), start_active_height: active_height, start_observe_height: observer_height, - validator_id: self.config.consensus_manager_config.dynamic_config.validator_id, - consensus_delay: self.config.consensus_manager_config.static_config.startup_delay, - timeouts: self.config.consensus_manager_config.static_config.timeouts.clone(), - sync_retry_interval: self - .config - .consensus_manager_config - .static_config - .sync_retry_interval, quorum_type, - }; - let consensus_fut = apollo_consensus::run_consensus( - run_consensus_args, - context, - votes_broadcast_channels.into(), - inbound_internal_receiver, - ); - - tokio::select! { - consensus_result = consensus_fut => { - match consensus_result { - Ok(_) => panic!("Consensus task finished unexpectedly"), - Err(e) => Err(e), - } - }, - network_result = network_task => { - panic!("Consensus' network task finished unexpectedly: {network_result:?}"); - } - stream_handler_result = stream_handler_task => { - panic!("Consensus' stream handler task finished unexpectedly: {stream_handler_result:?}"); - } + config_manager_client: Some(Arc::clone(&self.config_manager_client)), } } @@ -244,11 +289,15 @@ impl ConsensusManager { .expect("Failed to revert block at height {height} in the batcher"); }; + const BATCHER_REVERT_COMPONENT_DATA: RevertComponentData = RevertComponentData { + name: "Batcher", + revert_metric: CONSENSUS_REVERTED_BATCHER_UP_TO_AND_INCLUDING, + }; revert_blocks_and_eternal_pending( batcher_height_marker, revert_up_to_and_including, revert_blocks_fn, - "Batcher", + &BATCHER_REVERT_COMPONENT_DATA, ) .await; } @@ -278,8 +327,7 @@ pub fn create_consensus_manager( impl ComponentStarter for ConsensusManager { async fn start(&mut self) { info!("Starting component {}.", short_type_name::()); - self.run() - .await - .unwrap_or_else(|e| panic!("Failed to start ConsensusManager component: {e:?}")) + register_metrics(); + self.run().await; } } diff --git a/crates/apollo_consensus_manager/src/metrics.rs b/crates/apollo_consensus_manager/src/metrics.rs index 8185239c7e7..a08e792180f 100644 --- a/crates/apollo_consensus_manager/src/metrics.rs +++ b/crates/apollo_consensus_manager/src/metrics.rs @@ -18,7 +18,21 @@ define_metrics!( LabeledMetricCounter { CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES, "apollo_consensus_proposals_num_dropped_messages", "The number of messages dropped by the consensus p2p component over the Proposals topic", init = 0, labels = NETWORK_BROADCAST_DROP_LABELS }, // Network events - LabeledMetricCounter { CONSENSUS_NETWORK_EVENTS, "apollo_consensus_network_events", "Network events counter by event type for consensus", init = 0, labels = EVENT_TYPE_LABELS } + LabeledMetricCounter { CONSENSUS_NETWORK_EVENTS, "apollo_consensus_network_events", "Network events counter by event type for consensus", init = 0, labels = EVENT_TYPE_LABELS }, + MetricGauge { CONSENSUS_REVERTED_BATCHER_UP_TO_AND_INCLUDING, "apollo_consensus_reverted_batcher_up_to_and_including", "The block number up to which the batcher has reverted"}, }, ); + +pub(crate) fn register_metrics() { + CONSENSUS_NUM_CONNECTED_PEERS.register(); + CONSENSUS_NUM_BLACKLISTED_PEERS.register(); + CONSENSUS_VOTES_NUM_SENT_MESSAGES.register(); + CONSENSUS_VOTES_NUM_RECEIVED_MESSAGES.register(); + CONSENSUS_VOTES_NUM_DROPPED_MESSAGES.register(); + CONSENSUS_PROPOSALS_NUM_SENT_MESSAGES.register(); + CONSENSUS_PROPOSALS_NUM_RECEIVED_MESSAGES.register(); + CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES.register(); + CONSENSUS_NETWORK_EVENTS.register(); + CONSENSUS_REVERTED_BATCHER_UP_TO_AND_INCLUDING.register(); +} diff --git a/crates/apollo_consensus_orchestrator/resources/central_blob.json b/crates/apollo_consensus_orchestrator/resources/central_blob.json index 7f6a7dbfd1e..aca362303c7 100644 --- a/crates/apollo_consensus_orchestrator/resources/central_blob.json +++ b/crates/apollo_consensus_orchestrator/resources/central_blob.json @@ -418,6 +418,24 @@ "builtin_counters": { "range_check": 31, "pedersen": 4 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -455,6 +473,24 @@ "builtin_counters": { "range_check": 31, "pedersen": 4 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } }, "execute_call_info": { @@ -599,6 +635,24 @@ "builtin_counters": { "range_check": 31, "pedersen": 4 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -636,6 +690,24 @@ "builtin_counters": { "range_check": 31, "pedersen": 4 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } }, "fee_transfer_call_info": { @@ -780,6 +852,24 @@ "builtin_counters": { "range_check": 31, "pedersen": 4 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -817,6 +907,24 @@ "builtin_counters": { "range_check": 31, "pedersen": 4 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } }, "actual_fee": "0x26fe9d250e000", diff --git a/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info.json b/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info.json index c403b1e9cbe..f981f0c0a92 100644 --- a/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info.json +++ b/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info.json @@ -152,6 +152,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -189,6 +207,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } }, "fee_transfer_call_info": { @@ -333,6 +369,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -370,6 +424,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } }, "revert_error": null, @@ -520,6 +592,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -557,6 +647,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } -} +} \ No newline at end of file diff --git a/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info_reverted.json b/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info_reverted.json index df6d8e2a5aa..c731caed9c9 100644 --- a/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info_reverted.json +++ b/crates/apollo_consensus_orchestrator/resources/central_transaction_execution_info_reverted.json @@ -153,6 +153,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -190,6 +208,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } }, "revert_error": "Insufficient fee token balance. Fee: 1, balance: low/high 2/3.", @@ -340,6 +376,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } ], @@ -377,6 +431,24 @@ "builtin_counters": { "pedersen": 4, "range_check": 31 + }, + "syscalls_usage": { + "CallContract": { + "call_count": 7, + "linear_factor": 0 + }, + "StorageRead": { + "call_count": 4, + "linear_factor": 0 + }, + "StorageWrite": { + "call_count": 4, + "linear_factor": 0 + }, + "EmitEvent": { + "call_count": 2, + "linear_factor": 0 + } } } -} +} \ No newline at end of file diff --git a/crates/apollo_consensus_orchestrator/resources/orchestrator_versioned_constants_0_14_1.json b/crates/apollo_consensus_orchestrator/resources/orchestrator_versioned_constants_0_14_1.json index f06fc906940..858127a72ab 100644 --- a/crates/apollo_consensus_orchestrator/resources/orchestrator_versioned_constants_0_14_1.json +++ b/crates/apollo_consensus_orchestrator/resources/orchestrator_versioned_constants_0_14_1.json @@ -1,7 +1,7 @@ { "gas_price_max_change_denominator": 48, - "gas_target": 4000000000, - "max_block_size": 5000000000, + "gas_target": 4800000000, + "max_block_size": 6000000000, "min_gas_price": "0x186a0", "l1_gas_price_margin_percent": 10 } \ No newline at end of file diff --git a/crates/apollo_consensus_orchestrator/src/build_proposal.rs b/crates/apollo_consensus_orchestrator/src/build_proposal.rs index 39134803117..29077a1e7f9 100644 --- a/crates/apollo_consensus_orchestrator/src/build_proposal.rs +++ b/crates/apollo_consensus_orchestrator/src/build_proposal.rs @@ -26,7 +26,7 @@ use apollo_protobuf::consensus::{ TransactionBatch, }; use apollo_time::time::{Clock, DateTime}; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::consensus_transaction::InternalConsensusTransaction; use starknet_api::core::ContractAddress; use starknet_api::data_availability::L1DataAvailabilityMode; @@ -241,7 +241,7 @@ async fn get_proposal_content( .expect("Failed to broadcast proposal content"); } GetProposalContent::Finished { id, final_n_executed_txs } => { - let proposal_commitment = BlockHash(id.state_diff_commitment.0.0); + let proposal_commitment = ProposalCommitment(id.state_diff_commitment.0.0); content = truncate_to_executed_txs(&mut content, final_n_executed_txs); info!( diff --git a/crates/apollo_consensus_orchestrator/src/build_proposal_test.rs b/crates/apollo_consensus_orchestrator/src/build_proposal_test.rs index 907eb7d7542..e1c864768a5 100644 --- a/crates/apollo_consensus_orchestrator/src/build_proposal_test.rs +++ b/crates/apollo_consensus_orchestrator/src/build_proposal_test.rs @@ -12,7 +12,7 @@ use apollo_class_manager_types::transaction_converter::{ MockTransactionConverterTrait, TransactionConverterError, }; -use apollo_consensus::types::Round; +use apollo_consensus::types::{ProposalCommitment as ConsensusProposalCommitment, Round}; use apollo_consensus_orchestrator_config::config::ContextConfig; use apollo_infra::component_client::ClientError; use apollo_protobuf::consensus::{ConsensusBlockInfo, ProposalInit, ProposalPart}; @@ -21,8 +21,7 @@ use apollo_state_sync_types::errors::StateSyncError; use assert_matches::assert_matches; use blockifier::abi::constants::STORED_BLOCK_HASH_BUFFER; use futures::channel::mpsc; -use num_rational::Ratio; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::core::{ClassHash, ContractAddress}; use starknet_api::data_availability::L1DataAvailabilityMode; use tokio_util::sync::CancellationToken; @@ -39,7 +38,7 @@ use crate::test_utils::{ STATE_DIFF_COMMITMENT, TIMEOUT, }; -use crate::utils::{GasPriceParams, StreamSender}; +use crate::utils::{make_gas_price_params, GasPriceParams, StreamSender}; struct TestProposalBuildArguments { pub deps: TestDeps, @@ -89,17 +88,7 @@ fn create_proposal_build_arguments() -> (TestProposalBuildArguments, mpsc::Recei let stream_sender = StreamSender { proposal_sender }; let context_config = ContextConfig::default(); - let gas_price_params = GasPriceParams { - min_l1_gas_price_wei: GasPrice(context_config.min_l1_gas_price_wei), - max_l1_gas_price_wei: GasPrice(context_config.max_l1_gas_price_wei), - min_l1_data_gas_price_wei: GasPrice(context_config.min_l1_data_gas_price_wei), - max_l1_data_gas_price_wei: GasPrice(context_config.max_l1_data_gas_price_wei), - l1_data_gas_price_multiplier: Ratio::new( - context_config.l1_data_gas_price_multiplier_ppt, - 1000, - ), - l1_gas_tip_wei: GasPrice(context_config.l1_gas_tip_wei), - }; + let gas_price_params = make_gas_price_params(&context_config); let valid_proposals = Arc::new(Mutex::new(BuiltProposals::new())); let proposal_id = ProposalId(1); let cende_write_success = AbortOnDropHandle::new(tokio::spawn(async { true })); @@ -147,7 +136,7 @@ async fn build_proposal_succeed() { tokio::time::sleep(Duration::from_millis(100)).await; let res = build_proposal(proposal_args.into()).await.unwrap(); - assert_eq!(res, BlockHash::default()); + assert_eq!(res, ConsensusProposalCommitment::default()); } #[tokio::test] diff --git a/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs b/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs index de67f2fe638..86ebceaf61e 100644 --- a/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs +++ b/crates/apollo_consensus_orchestrator/src/cende/central_objects_test.rs @@ -28,6 +28,7 @@ use blockifier::execution::call_info::{ }; use blockifier::execution::contract_class::TrackedResource; use blockifier::execution::entry_point::{CallEntryPoint, CallType}; +use blockifier::execution::syscalls::vm_syscall_utils::{SyscallSelector, SyscallUsage}; use blockifier::fee::fee_checks::FeeCheckError; use blockifier::fee::receipt::TransactionReceipt; use blockifier::fee::resources::{ @@ -530,8 +531,13 @@ fn call_info() -> CallInfo { read_block_hash_values: vec![BlockHash(felt!("0xdeafbee"))], accessed_blocks: HashSet::from([BlockNumber(100)]), }, - // TODO(Meshi): insert relevant values. builtin_counters: execution_resources().prover_builtins(), + syscalls_usage: HashMap::from([ + (SyscallSelector::CallContract, SyscallUsage { call_count: 7, linear_factor: 0 }), + (SyscallSelector::StorageRead, SyscallUsage { call_count: 4, linear_factor: 0 }), + (SyscallSelector::StorageWrite, SyscallUsage { call_count: 4, linear_factor: 0 }), + (SyscallSelector::EmitEvent, SyscallUsage { call_count: 2, linear_factor: 0 }), + ]), } } diff --git a/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs b/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs index d049e6e744a..e259fca06e4 100644 --- a/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs +++ b/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs @@ -16,7 +16,7 @@ use apollo_batcher_types::batcher_types::{ ProposalId, StartHeightInput, }; -use apollo_batcher_types::communication::{BatcherClient, BatcherClientError}; +use apollo_batcher_types::communication::BatcherClient; use apollo_class_manager_types::transaction_converter::TransactionConverterTrait; use apollo_consensus::types::{ ConsensusContext, @@ -45,7 +45,6 @@ use apollo_time::time::Clock; use async_trait::async_trait; use futures::channel::{mpsc, oneshot}; use futures::SinkExt; -use num_rational::Ratio; use starknet_api::block::{ BlockHeaderWithoutHash, BlockNumber, @@ -73,7 +72,7 @@ use crate::metrics::{ CONSENSUS_L2_GAS_PRICE, }; use crate::orchestrator_versioned_constants::VersionedConstants; -use crate::utils::{convert_to_sn_api_block_info, GasPriceParams, StreamSender}; +use crate::utils::{convert_to_sn_api_block_info, make_gas_price_params, StreamSender}; use crate::validate_proposal::{ validate_proposal, BlockInfoValidation, @@ -188,13 +187,16 @@ impl SequencerConsensusContext { } else { L1DataAvailabilityMode::Calldata }; + let validators = if let Some(ids) = config.validator_ids.clone() { + ids.into_iter().collect() + } else { + (0..num_validators).map(|i| ValidatorId::from(DEFAULT_VALIDATOR_ID + i)).collect() + }; Self { config, deps, // TODO(Matan): Set the actual validator IDs (contract addresses). - validators: (0..num_validators) - .map(|i| ValidatorId::from(DEFAULT_VALIDATOR_ID + i)) - .collect(), + validators, valid_proposals: Arc::new(Mutex::new(BuiltProposals::new())), proposal_id: 0, current_height: None, @@ -249,20 +251,15 @@ impl ConsensusContext for SequencerConsensusContext { let stream_id = HeightAndRound(proposal_init.height.0, proposal_init.round); let stream_sender = self.start_stream(stream_id).await; - info!(?proposal_init, ?timeout, %proposal_id, "Building proposal"); + info!(?proposal_init, ?timeout, %proposal_id, "Start building proposal"); let cancel_token = CancellationToken::new(); let cancel_token_clone = cancel_token.clone(); - let gas_price_params = GasPriceParams { - min_l1_gas_price_wei: GasPrice(self.config.min_l1_gas_price_wei), - max_l1_gas_price_wei: GasPrice(self.config.max_l1_gas_price_wei), - min_l1_data_gas_price_wei: GasPrice(self.config.min_l1_data_gas_price_wei), - max_l1_data_gas_price_wei: GasPrice(self.config.max_l1_data_gas_price_wei), - l1_data_gas_price_multiplier: Ratio::new( - self.config.l1_data_gas_price_multiplier_ppt, - 1000, - ), - l1_gas_tip_wei: GasPrice(self.config.l1_gas_tip_wei), - }; + let gas_price_params = make_gas_price_params(&self.config); + let mut l2_gas_price = self.l2_gas_price; + if let Some(override_value) = self.config.override_l2_gas_price_fri { + info!("Overriding L2 gas price to {override_value} fri"); + l2_gas_price = GasPrice(override_value); + } let args = ProposalBuildArguments { deps: self.deps.clone(), batcher_timeout: timeout - self.config.build_proposal_margin_millis, @@ -273,7 +270,7 @@ impl ConsensusContext for SequencerConsensusContext { valid_proposals: Arc::clone(&self.valid_proposals), proposal_id, cende_write_success, - l2_gas_price: self.l2_gas_price, + l2_gas_price, builder_address: self.config.builder_address, cancel_token, previous_block_info: self.previous_block_info.clone(), @@ -338,7 +335,11 @@ impl ConsensusContext for SequencerConsensusContext { block_timestamp_window_seconds: self.config.block_timestamp_window_seconds, previous_block_info: self.previous_block_info.clone(), l1_da_mode: self.l1_da_mode, - l2_gas_price_fri: self.l2_gas_price, + l2_gas_price_fri: self + .config + .override_l2_gas_price_fri + .map(GasPrice) + .unwrap_or(self.l2_gas_price), }; self.validate_current_round_proposal( block_info_validation, @@ -470,8 +471,13 @@ impl ConsensusContext for SequencerConsensusContext { .collect(); let gas_target = VersionedConstants::latest_constants().gas_target; - if self.config.constant_l2_gas_price { - self.l2_gas_price = VersionedConstants::latest_constants().min_gas_price; + if let Some(override_value) = self.config.override_l2_gas_price_fri { + info!( + "L2 gas price ({}) is not updated, remains on override value of {override_value} \ + fri", + self.l2_gas_price.0 + ); + self.l2_gas_price = GasPrice(override_value); } else { self.l2_gas_price = calculate_next_base_gas_price(self.l2_gas_price, l2_gas_used, gas_target); @@ -695,21 +701,11 @@ impl SequencerConsensusContext { ) { let proposal_id = ProposalId(self.proposal_id); self.proposal_id += 1; - info!(?timeout, %proposal_id, %proposer, round=self.current_round, "Validating proposal."); + info!(?timeout, %proposal_id, %proposer, round=self.current_round, "Start validating proposal"); let cancel_token = CancellationToken::new(); let cancel_token_clone = cancel_token.clone(); - let gas_price_params = GasPriceParams { - min_l1_gas_price_wei: GasPrice(self.config.min_l1_gas_price_wei), - max_l1_gas_price_wei: GasPrice(self.config.max_l1_gas_price_wei), - min_l1_data_gas_price_wei: GasPrice(self.config.min_l1_data_gas_price_wei), - max_l1_data_gas_price_wei: GasPrice(self.config.max_l1_data_gas_price_wei), - l1_data_gas_price_multiplier: Ratio::new( - self.config.l1_data_gas_price_multiplier_ppt, - 1000, - ), - l1_gas_tip_wei: GasPrice(self.config.l1_gas_tip_wei), - }; + let gas_price_params = make_gas_price_params(&self.config); let args = ProposalValidateArguments { deps: self.deps.clone(), block_info_validation, @@ -744,7 +740,7 @@ impl SequencerConsensusContext { async fn interrupt_active_proposal(&mut self) { if let Some((token, handle)) = self.active_proposal.take() { token.cancel(); - handle.await.expect("Proposal task failed, propogating panic"); + handle.await.expect("Proposal task failed, propagating panic"); } } @@ -752,19 +748,13 @@ impl SequencerConsensusContext { &mut self, proposal_id: ProposalId, ) -> DecisionReachedResponse { - loop { - let input = DecisionReachedInput { proposal_id }; - match self.deps.batcher.decision_reached(input).await { - Ok(response) => break response, - Err(BatcherClientError::BatcherError(e)) => { - panic!("Failed to add decision due to batcher error: {e:?}"); - } - Err(BatcherClientError::ClientError(e)) => { - error!("Failed to add decision due to client error: {e:?}"); - } - } - tokio::task::yield_now().await; - } + // TODO(Dafna): Properly handle errors. Not all errors should be propagated as panics. We + // should have a way to report an error and continue to the next height. + self.deps + .batcher + .decision_reached(DecisionReachedInput { proposal_id }) + .await + .expect("Failed to add decision due to batcher error: {e:?}") } async fn batcher_add_sync_block(&mut self, sync_block: SyncBlock) { @@ -772,50 +762,34 @@ impl SequencerConsensusContext { "Adding sync block to Batcher for height {}", sync_block.block_header_without_hash.block_number, ); - loop { - match self.deps.batcher.add_sync_block(sync_block.clone()).await { - Ok(_) => break, - Err(BatcherClientError::BatcherError(e)) => { - panic!("Failed to add sync block due to batcher error: {e:?}"); - } - Err(BatcherClientError::ClientError(e)) => { - error!("Failed to add sync block due to client error: {e:?}"); - } - } - tokio::task::yield_now().await; - } + // TODO(Dafna): Properly handle errors. Not all errors should be propagated as panics. We + // should have a way to report an error and continue to the next height. + self.deps + .batcher + .add_sync_block(sync_block.clone()) + .await + .expect("Failed to add sync block due to batcher error: {e:?}"); } // `add_new_block` returns immediately, it doesn't wait for sync to fully process the block. async fn sync_add_new_block(&mut self, sync_block: SyncBlock) { - loop { - match self.deps.state_sync_client.add_new_block(sync_block.clone()).await { - Ok(_) => break, - Err(StateSyncClientError::StateSyncError(e)) => { - panic!("Failed to add new block due to sync error: {e:?}"); - } - Err(StateSyncClientError::ClientError(e)) => { - error!("Failed to add new block due to client error: {e:?}"); - } - } - tokio::task::yield_now().await; - } + // TODO(Dafna): Properly handle errors. Not all errors should be propagated as panics. We + // should have a way to report an error and continue to the next height. + self.deps + .state_sync_client + .add_new_block(sync_block.clone()) + .await + .expect("Failed to add new block due to sync error: {e:?}"); } async fn batcher_start_height(&mut self, height: BlockNumber) { - loop { - let input = StartHeightInput { height }; - match self.deps.batcher.start_height(input).await { - Ok(_) => break, - Err(BatcherClientError::BatcherError(e)) => { - panic!("Failed to start height due to batcher error: height={height} {e:?}"); - } - Err(BatcherClientError::ClientError(e)) => { - error!("Failed to start height due to client error: height={height} {e:?}"); - } - } - tokio::task::yield_now().await; - } + // TODO(Dafna): Properly handle errors. Not all errors should be propagated as panics. We + // should have a way to report an error and continue to the next height. + self.deps + .batcher + .start_height(StartHeightInput { height }) + .await + .expect("Failed to start height due to batcher error: {e:?}"); } } diff --git a/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context_test.rs b/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context_test.rs index 35c67a1a15c..10ad62cf762 100644 --- a/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context_test.rs +++ b/crates/apollo_consensus_orchestrator/src/sequencer_consensus_context_test.rs @@ -13,7 +13,14 @@ use apollo_l1_gas_price_types::errors::{ L1GasPriceProviderError, }; use apollo_l1_gas_price_types::{MockL1GasPriceProviderClient, PriceInfo, DEFAULT_ETH_TO_FRI_RATE}; -use apollo_protobuf::consensus::{ProposalFin, ProposalInit, ProposalPart, TransactionBatch, Vote}; +use apollo_protobuf::consensus::{ + ProposalCommitment, + ProposalFin, + ProposalInit, + ProposalPart, + TransactionBatch, + Vote, +}; use apollo_time::time::MockClock; use chrono::{TimeZone, Utc}; use futures::channel::mpsc; @@ -23,7 +30,6 @@ use futures::{FutureExt, SinkExt, StreamExt}; use metrics_exporter_prometheus::PrometheusBuilder; use rstest::rstest; use starknet_api::block::{ - BlockHash, BlockNumber, GasPrice, TEMP_ETH_BLOB_GAS_FEE_IN_WEI, @@ -44,6 +50,7 @@ use crate::test_utils::{ TIMEOUT, TX_BATCH, }; +use crate::utils::{apply_fee_transformations, make_gas_price_params}; #[tokio::test] async fn cancelled_proposal_aborts() { @@ -65,7 +72,7 @@ async fn cancelled_proposal_aborts() { #[tokio::test] async fn validate_proposal_success() { let (mut deps, _network) = create_test_and_network_deps(); - deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let mut context = deps.build_context(); // Initialize the context for a specific height, starting with round 0. @@ -83,7 +90,7 @@ async fn validate_proposal_success() { .unwrap(); content_sender .send(ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), })) .await .unwrap(); @@ -127,7 +134,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) { false => TX_BATCH.iter().take(TX_BATCH.len() - 1).cloned().collect(), }; let final_n_executed_txs = executed_transactions.len(); - deps.setup_deps_for_validate(BlockNumber(0), final_n_executed_txs); + deps.setup_deps_for_validate(BlockNumber(0), final_n_executed_txs, 1); let mut context = deps.build_context(); // Initialize the context for a specific height, starting with round 0. @@ -145,7 +152,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) { .await .unwrap(); let fin = ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), }); content_sender.send(fin.clone()).await.unwrap(); let fin_receiver = @@ -154,7 +161,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) { assert_eq!(fin_receiver.await.unwrap().0, STATE_DIFF_COMMITMENT.0.0); let init = ProposalInit { round: 1, ..Default::default() }; - context.repropose(BlockHash(STATE_DIFF_COMMITMENT.0.0), init).await; + context.repropose(ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), init).await; let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap(); assert_eq!(receiver.next().await.unwrap(), ProposalPart::Init(init)); assert_eq!(receiver.next().await.unwrap(), block_info); @@ -173,7 +180,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) { #[tokio::test] async fn proposals_from_different_rounds() { let (mut deps, _network) = create_test_and_network_deps(); - deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let mut context = deps.build_context(); // Initialize the context for a specific height, starting with round 0. context.set_height_and_round(BlockNumber(0), 0).await; @@ -185,7 +192,7 @@ async fn proposals_from_different_rounds() { let prop_part_executed_count = ProposalPart::ExecutedTransactionCount(INTERNAL_TX_BATCH.len().try_into().unwrap()); let prop_part_fin = ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), }); // The proposal from the past round is ignored. @@ -230,7 +237,7 @@ async fn proposals_from_different_rounds() { #[tokio::test] async fn interrupt_active_proposal() { let (mut deps, _network) = create_test_and_network_deps(); - deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let mut context = deps.build_context(); // Initialize the context for a specific height, starting with round 0. context.set_height_and_round(BlockNumber(0), 0).await; @@ -255,7 +262,7 @@ async fn interrupt_active_proposal() { .unwrap(); content_sender_1 .send(ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), })) .await .unwrap(); @@ -279,7 +286,7 @@ async fn build_proposal() { let before: u64 = chrono::Utc::now().timestamp().try_into().expect("Timestamp conversion failed"); let (mut deps, mut network) = create_test_and_network_deps(); - deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let mut context = deps.build_context(); let fin_receiver = context.build_proposal(ProposalInit::default(), TIMEOUT).await; // Test proposal parts. @@ -304,7 +311,7 @@ async fn build_proposal() { assert_eq!( receiver.next().await.unwrap(), ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), }) ); assert!(receiver.next().await.is_none()); @@ -313,7 +320,7 @@ async fn build_proposal() { #[tokio::test] async fn build_proposal_cende_failure() { let (mut deps, _network) = create_test_and_network_deps(); - deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let mut mock_cende_context = MockCendeContext::new(); mock_cende_context .expect_write_prev_height_blob() @@ -329,7 +336,7 @@ async fn build_proposal_cende_failure() { #[tokio::test] async fn build_proposal_cende_incomplete() { let (mut deps, _network) = create_test_and_network_deps(); - deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let mut mock_cende_context = MockCendeContext::new(); mock_cende_context .expect_write_prev_height_blob() @@ -388,7 +395,7 @@ async fn propose_then_repropose(#[case] execute_all_txs: bool) { true => TX_BATCH.to_vec(), false => TX_BATCH.iter().take(TX_BATCH.len() - 1).cloned().collect(), }; - deps.setup_deps_for_build(BlockNumber(0), transactions.len()); + deps.setup_deps_for_build(BlockNumber(0), transactions.len(), 1); let mut context = deps.build_context(); // Build proposal. let fin_receiver = context.build_proposal(ProposalInit::default(), TIMEOUT).await; @@ -405,7 +412,7 @@ async fn propose_then_repropose(#[case] execute_all_txs: bool) { // Re-propose. context .repropose( - BlockHash(STATE_DIFF_COMMITMENT.0.0), + ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), ProposalInit { round: 1, ..Default::default() }, ) .await; @@ -453,7 +460,7 @@ async fn eth_to_fri_rate_out_of_range() { #[tokio::test] async fn gas_price_limits(#[case] maximum: bool) { let (mut deps, _network) = create_test_and_network_deps(); - deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); let context_config = ContextConfig::default(); let min_gas_price = context_config.min_l1_gas_price_wei; let min_data_price = context_config.min_l1_data_gas_price_wei; @@ -504,7 +511,7 @@ async fn gas_price_limits(#[case] maximum: bool) { .unwrap(); content_sender .send(ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), })) .await .unwrap(); @@ -513,7 +520,7 @@ async fn gas_price_limits(#[case] maximum: bool) { // the proposal should be still be valid due to the clamping of limit prices. let fin_receiver = context.validate_proposal(ProposalInit::default(), TIMEOUT, content_receiver).await; - assert_eq!(fin_receiver.await, Ok(BlockHash(STATE_DIFF_COMMITMENT.0.0))); + assert_eq!(fin_receiver.await, Ok(ProposalCommitment(STATE_DIFF_COMMITMENT.0.0))); } #[tokio::test] @@ -525,7 +532,7 @@ async fn decision_reached_sends_correct_values() { // We need to create a valid proposal to call decision_reached on. // // 1. Build proposal setup starts. - deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); const BLOCK_TIME_STAMP_SECONDS: u64 = 123456; let mut clock = MockClock::new(); @@ -565,7 +572,10 @@ async fn decision_reached_sends_correct_values() { ..Default::default() }; - context.decision_reached(BlockHash(STATE_DIFF_COMMITMENT.0.0), vec![vote]).await.unwrap(); + context + .decision_reached(ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), vec![vote]) + .await + .unwrap(); let metrics = recorder.handle().render(); CONSENSUS_L2_GAS_PRICE @@ -578,7 +588,7 @@ async fn decision_reached_sends_correct_values() { #[tokio::test] async fn oracle_fails_on_startup(#[case] l1_oracle_failure: bool) { let (mut deps, mut network) = create_test_and_network_deps(); - deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); if l1_oracle_failure { let mut l1_prices_oracle_client = MockL1GasPriceProviderClient::new(); @@ -638,7 +648,7 @@ async fn oracle_fails_on_startup(#[case] l1_oracle_failure: bool) { assert_eq!( receiver.next().await.unwrap(), ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), }) ); assert!(receiver.next().await.is_none()); @@ -653,8 +663,8 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) { let (mut deps, mut network) = create_test_and_network_deps(); // Validate block number 0, call decision_reached to save the previous block info (block 0), and // attempt to build_proposal on block number 1. - deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len()); - deps.setup_deps_for_build(BlockNumber(1), INTERNAL_TX_BATCH.len()); + deps.setup_deps_for_validate(BlockNumber(0), INTERNAL_TX_BATCH.len(), 1); + deps.setup_deps_for_build(BlockNumber(1), INTERNAL_TX_BATCH.len(), 1); // set up batcher decision_reached deps.batcher.expect_decision_reached().times(1).return_once(|_| { @@ -726,22 +736,22 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) { .unwrap(); content_sender .send(ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), })) .await .unwrap(); let fin_receiver = context.validate_proposal(ProposalInit::default(), TIMEOUT, content_receiver).await; content_sender.close_channel(); - let block_hash = fin_receiver.await.unwrap().0; - assert_eq!(block_hash, STATE_DIFF_COMMITMENT.0.0); + let proposal_commitment = fin_receiver.await.unwrap(); + assert_eq!(proposal_commitment.0, STATE_DIFF_COMMITMENT.0.0); // Decision reached context .decision_reached( - BlockHash(block_hash), - vec![Vote { block_hash: Some(BlockHash(block_hash)), ..Default::default() }], + proposal_commitment, + vec![Vote { proposal_commitment: Some(proposal_commitment), ..Default::default() }], ) .await .unwrap(); @@ -779,30 +789,82 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) { assert_eq!( receiver.next().await.unwrap(), ProposalPart::Fin(ProposalFin { - proposal_commitment: BlockHash(STATE_DIFF_COMMITMENT.0.0), + proposal_commitment: ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), }) ); assert!(receiver.next().await.is_none()); assert_eq!(fin_receiver.await.unwrap().0, STATE_DIFF_COMMITMENT.0.0); } +// L2 gas is a bit above the minimum gas price. +const ODDLY_SPECIFIC_L2_GAS_PRICE: u128 = 9999999999; +const ODDLY_SPECIFIC_L1_GAS_PRICE: u128 = 1234567890; +const ODDLY_SPECIFIC_L1_DATA_GAS_PRICE: u128 = 987654321; +const ODDLY_SPECIFIC_CONVERSION_RATE: u128 = 12345678901234567890; + +// If we use low numbers for fri/wei we have to make sure the conversion (eth to fri) and eth-to-wei +// factor (wei to eth) don't go below zero in either direction. Typically the gas price (in +// particular the data gas price) can be as low as 1, so the eth-to-fri rate must be above 10^18. +// That also means that the L2 gas (in fri) must be bigger than the ratio of the conversion rate and +// the eth-to-wei factor. Must use a large enough number that conversion to wei works +const LOW_OVERRIDE_L2_GAS_PRICE: u128 = 25; // FRI +// ETH_TO_FRI_RATE must be larger/equal to 10^18 (wei to eth conversion factor) +const LOW_OVERRIDE_CONVERSION_RATE: u128 = u128::pow(10, 19); + +// If we use really low L2 gas price, the block will fail to build. +const LOW_OVERRIDE_L2_GAS_PRICE_FAIL: u128 = 1; // FRI + #[rstest] -#[case::constant_l2_gas_price_true(true, GasAmount::default())] -#[case::constant_l2_gas_price_false(false, VersionedConstants::latest_constants().max_block_size)] +#[case::dont_override_prices(None, None, None, None, true)] +#[case::override_l2_gas_price(Some(ODDLY_SPECIFIC_L2_GAS_PRICE), None, None, None, true)] +#[case::override_l1_gas_price(None, Some(ODDLY_SPECIFIC_L1_GAS_PRICE), None, None, true)] +#[case::override_l1_data_gas_price(None, None, Some(ODDLY_SPECIFIC_L1_DATA_GAS_PRICE), None, true)] +#[case::override_all_prices( + Some(ODDLY_SPECIFIC_L2_GAS_PRICE), + Some(ODDLY_SPECIFIC_L1_GAS_PRICE), + Some(ODDLY_SPECIFIC_L1_DATA_GAS_PRICE), + None, + true +)] +#[case::override_everything( + Some(ODDLY_SPECIFIC_L2_GAS_PRICE), + Some(ODDLY_SPECIFIC_L1_GAS_PRICE), + Some(ODDLY_SPECIFIC_L1_DATA_GAS_PRICE), + Some(ODDLY_SPECIFIC_CONVERSION_RATE), + true +)] +#[case::low_overrides( + Some(LOW_OVERRIDE_L2_GAS_PRICE), + Some(1), + Some(1), + Some(LOW_OVERRIDE_CONVERSION_RATE), + true +)] +#[case::low_l2_gas_price_fail( + Some(LOW_OVERRIDE_L2_GAS_PRICE_FAIL), + None, + None, + Some(LOW_OVERRIDE_CONVERSION_RATE), + false +)] #[tokio::test] -async fn constant_l2_gas_price_behavior( - #[case] constant_l2_gas_price: bool, - #[case] mock_l2_gas_used: GasAmount, +async fn override_prices_behavior( + #[case] override_l2_gas_price_fri: Option, + #[case] override_l1_gas_price_wei: Option, + #[case] override_l1_data_gas_price_wei: Option, + #[case] override_eth_to_fri_rate: Option, + #[case] build_success: bool, ) { - let (mut deps, _network) = create_test_and_network_deps(); + // Use high gas usage to ensure the L2 gas price is high. + let mock_l2_gas_used = VersionedConstants::latest_constants().max_block_size; - let recorder = PrometheusBuilder::new().build_recorder(); - let _recorder_guard = metrics::set_default_local_recorder(&recorder); + let (mut deps, _network) = create_test_and_network_deps(); // Setup dependencies and mocks. - deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len()); - - deps.batcher.expect_decision_reached().times(1).return_once(move |_| { + #[allow(clippy::as_conversions)] + deps.setup_deps_for_build(BlockNumber(0), INTERNAL_TX_BATCH.len(), build_success as usize); + deps.l1_gas_price_provider.expect_get_eth_to_fri_rate().returning(|_| Ok(ETH_TO_FRI_RATE)); + deps.batcher.expect_decision_reached().return_once(move |_| { Ok(DecisionReachedResponse { state_diff: ThinStateDiff::default(), l2_gas_used: mock_l2_gas_used, @@ -810,33 +872,107 @@ async fn constant_l2_gas_price_behavior( }) }); - deps.state_sync_client.expect_add_new_block().times(1).return_once(|_| Ok(())); - deps.cende_ambassador.expect_prepare_blob_for_next_height().times(1).return_once(|_| Ok(())); + deps.state_sync_client.expect_add_new_block().return_once(|_| Ok(())); + deps.cende_ambassador.expect_prepare_blob_for_next_height().return_once(|_| Ok(())); - let context_config = ContextConfig { constant_l2_gas_price, ..Default::default() }; + let context_config = ContextConfig { + override_l2_gas_price_fri, + override_l1_gas_price_wei, + override_l1_data_gas_price_wei, + override_eth_to_fri_rate, + ..Default::default() + }; let mut context = deps.build_context(); context.config = context_config; + let min_gas_price = VersionedConstants::latest_constants().min_gas_price.0; + let gas_price_params = make_gas_price_params(&context.config); + let mut expected_l1_prices = PriceInfo { + base_fee_per_gas: GasPrice(TEMP_ETH_GAS_FEE_IN_WEI), + blob_fee: GasPrice(TEMP_ETH_BLOB_GAS_FEE_IN_WEI), + }; + apply_fee_transformations(&mut expected_l1_prices, &gas_price_params); + // Run proposal and decision logic. - let _fin_receiver = context.build_proposal(ProposalInit::default(), TIMEOUT).await.await; + let fin_result = context.build_proposal(ProposalInit::default(), TIMEOUT).await.await; + + // In cases where we expect the batcher to fail the block build. + if !build_success { + assert!(fin_result.is_err()); + return; + } + context - .decision_reached(BlockHash(STATE_DIFF_COMMITMENT.0.0), vec![Vote::default()]) + .decision_reached(ProposalCommitment(STATE_DIFF_COMMITMENT.0.0), vec![Vote::default()]) .await .unwrap(); - let min_gas_price = VersionedConstants::latest_constants().min_gas_price.0; let actual_l2_gas_price = context.l2_gas_price.0; - if constant_l2_gas_price { + let previous_block = context.previous_block_info.clone().unwrap(); + let actual_l1_gas_price = previous_block.l1_gas_price_wei.0; + let actual_l1_data_gas_price = previous_block.l1_data_gas_price_wei.0; + let actual_conversion_rate = previous_block.eth_to_fri_rate; + + if let Some(override_l2_gas_price) = override_l2_gas_price_fri { + // In this case the L2 gas price must match the given override. assert_eq!( - actual_l2_gas_price, min_gas_price, - "Expected L2 gas price to match constant min_gas_price" + actual_l2_gas_price, override_l2_gas_price, + "Expected L2 gas price ({actual_l2_gas_price}) to match override_l2_gas_price \ + ({override_l2_gas_price})", ); } else { + // In this case the regular L2 gas calculation takes place, and gives a higher price. assert!( actual_l2_gas_price > min_gas_price, - "Expected L2 gas price > min ({min_gas_price}) due to high usage (EIP-1559), but got \ - {actual_l2_gas_price}" + "Expected L2 gas price ({actual_l2_gas_price}) > minimum l2 gas price \ + ({min_gas_price}) due to high usage (EIP-1559)", + ); + } + + if let Some(override_l1_gas_price) = override_l1_gas_price_wei { + assert_eq!( + actual_l1_gas_price, override_l1_gas_price, + "Expected L1 gas price ({actual_l1_gas_price}) to match input l1 gas price \ + ({override_l1_gas_price})", + ); + } else { + assert_eq!( + actual_l1_gas_price, expected_l1_prices.base_fee_per_gas.0, + "Expected L1 gas price ({actual_l1_gas_price}) to match input l1 gas price ({})", + expected_l1_prices.base_fee_per_gas.0 + ); + } + + if let Some(override_l1_data_gas_price) = override_l1_data_gas_price_wei { + assert_eq!( + actual_l1_data_gas_price, override_l1_data_gas_price, + "Expected L1 data gas price ({actual_l1_data_gas_price}) to match input l1 data gas \ + price ({override_l1_data_gas_price})", + ); + } else { + assert_eq!( + actual_l1_data_gas_price, expected_l1_prices.blob_fee.0, + "Expected L1 data gas price ({actual_l1_data_gas_price}) to match input l1 data gas \ + price ({})", + expected_l1_prices.blob_fee.0 + ); + } + + if let Some(override_eth_to_fri_rate) = override_eth_to_fri_rate { + assert_eq!( + actual_conversion_rate, override_eth_to_fri_rate, + "Expected conversion rate ({}) to match input conversion rate ({})", + actual_conversion_rate, override_eth_to_fri_rate + ); + } else { + // Note: the "default eth to fri rate" is actually just 10^18 (eth to wei). + // This is set in the default expectations and is used by many other tests. + // So we'll just assume that this is the "real" conversion rate, unless overriden. + assert_eq!( + actual_conversion_rate, ETH_TO_FRI_RATE, + "Expected conversion rate ({}) to match default conversion rate ({})", + actual_conversion_rate, ETH_TO_FRI_RATE ); } } diff --git a/crates/apollo_consensus_orchestrator/src/test_utils.rs b/crates/apollo_consensus_orchestrator/src/test_utils.rs index 1f458c23450..523ca7b6407 100644 --- a/crates/apollo_consensus_orchestrator/src/test_utils.rs +++ b/crates/apollo_consensus_orchestrator/src/test_utils.rs @@ -121,29 +121,32 @@ impl TestDeps { &mut self, block_number: BlockNumber, final_n_executed_txs: usize, + number_of_times: usize, ) { assert!(final_n_executed_txs <= INTERNAL_TX_BATCH.len()); self.setup_default_expectations(); let proposal_id = Arc::new(OnceLock::new()); let proposal_id_clone = Arc::clone(&proposal_id); - self.batcher.expect_propose_block().times(1).returning(move |input: ProposeBlockInput| { - proposal_id_clone.set(input.proposal_id).unwrap(); - Ok(()) - }); + self.batcher.expect_propose_block().times(number_of_times).returning( + move |input: ProposeBlockInput| { + proposal_id_clone.set(input.proposal_id).unwrap(); + Ok(()) + }, + ); self.batcher .expect_start_height() .times(1) .withf(move |input| input.height == block_number) .return_const(Ok(())); let proposal_id_clone = Arc::clone(&proposal_id); - self.batcher.expect_get_proposal_content().times(1).returning(move |input| { + self.batcher.expect_get_proposal_content().times(number_of_times).returning(move |input| { assert_eq!(input.proposal_id, *proposal_id_clone.get().unwrap()); Ok(GetProposalContentResponse { content: GetProposalContent::Txs(INTERNAL_TX_BATCH.clone()), }) }); let proposal_id_clone = Arc::clone(&proposal_id); - self.batcher.expect_get_proposal_content().times(1).returning(move |input| { + self.batcher.expect_get_proposal_content().times(number_of_times).returning(move |input| { assert_eq!(input.proposal_id, *proposal_id_clone.get().unwrap()); Ok(GetProposalContentResponse { content: GetProposalContent::Finished { @@ -158,12 +161,13 @@ impl TestDeps { &mut self, block_number: BlockNumber, final_n_executed_txs: usize, + number_of_times: usize, ) { assert!(final_n_executed_txs <= INTERNAL_TX_BATCH.len()); self.setup_default_expectations(); let proposal_id = Arc::new(OnceLock::new()); let proposal_id_clone = Arc::clone(&proposal_id); - self.batcher.expect_validate_block().times(1).returning( + self.batcher.expect_validate_block().times(number_of_times).returning( move |input: ValidateBlockInput| { proposal_id_clone.set(input.proposal_id).unwrap(); Ok(()) @@ -174,7 +178,7 @@ impl TestDeps { .withf(move |input| input.height == block_number) .return_const(Ok(())); let proposal_id_clone = Arc::clone(&proposal_id); - self.batcher.expect_send_proposal_content().times(1).returning( + self.batcher.expect_send_proposal_content().times(number_of_times).returning( move |input: SendProposalContentInput| { assert_eq!(input.proposal_id, *proposal_id_clone.get().unwrap()); let SendProposalContent::Txs(txs) = input.content else { @@ -185,7 +189,7 @@ impl TestDeps { }, ); let proposal_id_clone = Arc::clone(&proposal_id); - self.batcher.expect_send_proposal_content().times(1).returning( + self.batcher.expect_send_proposal_content().times(number_of_times).returning( move |input: SendProposalContentInput| { assert_eq!(input.proposal_id, *proposal_id_clone.get().unwrap()); assert_eq!(input.content, SendProposalContent::Finish(final_n_executed_txs)); diff --git a/crates/apollo_consensus_orchestrator/src/utils.rs b/crates/apollo_consensus_orchestrator/src/utils.rs index 658db462d97..2560c1be43a 100644 --- a/crates/apollo_consensus_orchestrator/src/utils.rs +++ b/crates/apollo_consensus_orchestrator/src/utils.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use apollo_consensus_orchestrator_config::config::ContextConfig; use apollo_l1_gas_price_types::{L1GasPriceProviderClient, PriceInfo, DEFAULT_ETH_TO_FRI_RATE}; use apollo_protobuf::consensus::{ConsensusBlockInfo, ProposalPart}; use apollo_state_sync_types::communication::{ @@ -41,6 +42,7 @@ impl StreamSender { } } +#[derive(Debug)] pub(crate) struct GasPriceParams { pub min_l1_gas_price_wei: GasPrice, pub max_l1_gas_price_wei: GasPrice, @@ -48,6 +50,9 @@ pub(crate) struct GasPriceParams { pub min_l1_data_gas_price_wei: GasPrice, pub l1_data_gas_price_multiplier: Ratio, pub l1_gas_tip_wei: GasPrice, + pub override_l1_gas_price_wei: Option, + pub override_l1_data_gas_price_wei: Option, + pub override_eth_to_fri_rate: Option, } impl From for BuildProposalError { @@ -82,6 +87,8 @@ pub(crate) async fn get_oracle_rate_and_prices( l1_gas_price_provider_client.get_eth_to_fri_rate(timestamp), l1_gas_price_provider_client.get_price_info(BlockTimestamp(timestamp)) ); + let mut return_values; + if price_info.is_err() { warn!("Failed to get l1 gas price from provider: {:?}", price_info); CONSENSUS_L1_GAS_PRICE_PROVIDER_ERROR.increment(1); @@ -91,39 +98,65 @@ pub(crate) async fn get_oracle_rate_and_prices( } if let (Ok(eth_to_strk_rate), Ok(mut price_info)) = (eth_to_strk_rate, price_info) { - info!("eth_to_strk_rate: {eth_to_strk_rate}, l1 gas price: {price_info:?}"); + // Both L1 prices and rate are Ok, so we can use them. + info!( + "raw eth_to_strk_rate (from oracle): {eth_to_strk_rate}, raw l1 gas price wei (from \ + provider): {price_info:?}" + ); apply_fee_transformations(&mut price_info, gas_price_params); - return (eth_to_strk_rate, price_info); + return_values = (eth_to_strk_rate, price_info); + } else { + // One or both have failed, need to use previous block info (or default values) + match previous_block_info { + Some(block_info) => { + let prev_l1_gas_price = PriceInfo { + base_fee_per_gas: block_info.l1_gas_price_wei, + blob_fee: block_info.l1_data_gas_price_wei, + }; + info!( + "Using values from previous block info. eth_to_strk_rate: {}, l1 gas price: \ + {:?}", + block_info.eth_to_fri_rate, prev_l1_gas_price + ); + return_values = (block_info.eth_to_fri_rate, prev_l1_gas_price); + } + None => { + let l1_gas_price = PriceInfo { + base_fee_per_gas: gas_price_params.min_l1_gas_price_wei, + blob_fee: gas_price_params.min_l1_data_gas_price_wei, + }; + info!( + "No previous block info available, using default values. eth_to_strk_rate: \ + {}, l1 gas price: {:?}", + DEFAULT_ETH_TO_FRI_RATE, l1_gas_price + ); + return_values = (DEFAULT_ETH_TO_FRI_RATE, l1_gas_price); + } + } } - match previous_block_info { - Some(block_info) => { - let prev_l1_gas_price = PriceInfo { - base_fee_per_gas: block_info.l1_gas_price_wei, - blob_fee: block_info.l1_data_gas_price_wei, - }; - info!( - "Using values from previous block info. eth_to_strk_rate: {}, l1 gas price: {:?}", - block_info.eth_to_fri_rate, prev_l1_gas_price - ); - (block_info.eth_to_fri_rate, prev_l1_gas_price) - } - None => { - let l1_gas_price = PriceInfo { - base_fee_per_gas: gas_price_params.min_l1_gas_price_wei, - blob_fee: gas_price_params.min_l1_data_gas_price_wei, - }; - info!( - "No previous block info available, using default values. eth_to_strk_rate: {}, l1 \ - gas price: {:?}", - DEFAULT_ETH_TO_FRI_RATE, l1_gas_price - ); - (DEFAULT_ETH_TO_FRI_RATE, l1_gas_price) - } + // If there is an override to L1 gas price or data gas price, apply it here: + if let Some(override_value) = gas_price_params.override_l1_gas_price_wei { + info!("Overriding L1 gas price to {override_value} wei"); + return_values.1.base_fee_per_gas = override_value; + } + if let Some(override_value) = gas_price_params.override_l1_data_gas_price_wei { + info!("Overriding L1 data gas price to {override_value} wei"); + return_values.1.blob_fee = override_value; } + + if let Some(override_value) = gas_price_params.override_eth_to_fri_rate { + info!("Overriding conversion rate to {override_value}"); + return_values.0 = override_value; + } + + return_values } -fn apply_fee_transformations(price_info: &mut PriceInfo, gas_price_params: &GasPriceParams) { +pub(crate) fn apply_fee_transformations( + price_info: &mut PriceInfo, + gas_price_params: &GasPriceParams, +) { price_info.base_fee_per_gas = price_info .base_fee_per_gas .saturating_add(gas_price_params.l1_gas_tip_wei) @@ -212,3 +245,17 @@ pub(crate) fn truncate_to_executed_txs( executed_content } + +pub(crate) fn make_gas_price_params(config: &ContextConfig) -> GasPriceParams { + GasPriceParams { + min_l1_gas_price_wei: GasPrice(config.min_l1_gas_price_wei), + max_l1_gas_price_wei: GasPrice(config.max_l1_gas_price_wei), + min_l1_data_gas_price_wei: GasPrice(config.min_l1_data_gas_price_wei), + max_l1_data_gas_price_wei: GasPrice(config.max_l1_data_gas_price_wei), + l1_data_gas_price_multiplier: Ratio::new(config.l1_data_gas_price_multiplier_ppt, 1000), + l1_gas_tip_wei: GasPrice(config.l1_gas_tip_wei), + override_l1_gas_price_wei: config.override_l1_gas_price_wei.map(GasPrice), + override_l1_data_gas_price_wei: config.override_l1_data_gas_price_wei.map(GasPrice), + override_eth_to_fri_rate: config.override_eth_to_fri_rate, + } +} diff --git a/crates/apollo_consensus_orchestrator/src/validate_proposal.rs b/crates/apollo_consensus_orchestrator/src/validate_proposal.rs index 04aa63e91d0..07b6defa4fd 100644 --- a/crates/apollo_consensus_orchestrator/src/validate_proposal.rs +++ b/crates/apollo_consensus_orchestrator/src/validate_proposal.rs @@ -20,10 +20,10 @@ use apollo_l1_gas_price_types::errors::{EthToStrkOracleClientError, L1GasPriceCl use apollo_l1_gas_price_types::L1GasPriceProviderClient; use apollo_protobuf::consensus::{ConsensusBlockInfo, ProposalFin, ProposalPart, TransactionBatch}; use apollo_state_sync_types::communication::StateSyncClient; -use apollo_time::time::{sleep_until, Clock, DateTime}; +use apollo_time::time::{Clock, ClockExt, DateTime}; use futures::channel::mpsc; use futures::StreamExt; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::consensus_transaction::InternalConsensusTransaction; use starknet_api::data_availability::L1DataAvailabilityMode; use starknet_api::transaction::TransactionHash; @@ -182,7 +182,7 @@ pub(crate) async fn validate_proposal( "validating proposal parts".to_string(), )); } - _ = sleep_until(deadline, args.deps.clock.as_ref()) => { + _ = args.deps.clock.sleep_until(deadline) => { batcher_abort_proposal(args.deps.batcher.as_ref(), args.proposal_id).await; return Err(ValidateProposalError::ValidationTimeout( "validating proposal parts".to_string(), @@ -361,7 +361,7 @@ async fn await_second_proposal_part( "waiting for second proposal part".to_string(), )) } - _ = sleep_until(deadline, clock) => { + _ = clock.sleep_until(deadline) => { Err(ValidateProposalError::ValidationTimeout( "waiting for second proposal part".to_string(), )) @@ -463,7 +463,7 @@ async fn handle_proposal_part( unreachable!("Unexpected batcher status for fin: {status:?}"); } }; - let batcher_block_id = BlockHash(response_id.state_diff_commitment.0.0); + let batcher_block_id = ProposalCommitment(response_id.state_diff_commitment.0.0); info!( network_block_id = ?fin.proposal_commitment, @@ -561,6 +561,8 @@ async fn batcher_abort_proposal(batcher: &dyn BatcherClient, proposal_id: Propos return; } + // TODO(Dafna): Properly handle errors. Not all errors should be propagated as panics. + // We should have a way to report an error and continue to the next height. Err(BatcherClientError::BatcherError(e)) => { panic!("Batcher failed to abort proposal {proposal_id:?}: {e:?}"); } diff --git a/crates/apollo_consensus_orchestrator/src/validate_proposal_test.rs b/crates/apollo_consensus_orchestrator/src/validate_proposal_test.rs index e2430fc9043..4a1506c3ecd 100644 --- a/crates/apollo_consensus_orchestrator/src/validate_proposal_test.rs +++ b/crates/apollo_consensus_orchestrator/src/validate_proposal_test.rs @@ -12,13 +12,17 @@ use apollo_batcher_types::batcher_types::{ use apollo_batcher_types::communication::BatcherClientError; use apollo_consensus_orchestrator_config::config::ContextConfig; use apollo_infra::component_client::ClientError; -use apollo_protobuf::consensus::{ProposalFin, ProposalPart, TransactionBatch}; +use apollo_protobuf::consensus::{ + ProposalCommitment as ConsensusProposalCommitment, + ProposalFin, + ProposalPart, + TransactionBatch, +}; use assert_matches::assert_matches; use futures::channel::mpsc; use futures::SinkExt; -use num_rational::Ratio; use rstest::rstest; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::core::StateDiffCommitment; use starknet_api::data_availability::L1DataAvailabilityMode; use starknet_api::hash::PoseidonHash; @@ -35,7 +39,7 @@ use crate::test_utils::{ TIMEOUT, TX_BATCH, }; -use crate::utils::GasPriceParams; +use crate::utils::{make_gas_price_params, GasPriceParams}; use crate::validate_proposal::{ validate_proposal, within_margin, @@ -89,17 +93,7 @@ fn create_proposal_validate_arguments() let valid_proposals = Arc::new(Mutex::new(BuiltProposals::new())); let (content_sender, content_receiver) = mpsc::channel(CHANNEL_SIZE); let context_config = ContextConfig::default(); - let gas_price_params = GasPriceParams { - min_l1_gas_price_wei: GasPrice(context_config.min_l1_gas_price_wei), - max_l1_gas_price_wei: GasPrice(context_config.max_l1_gas_price_wei), - min_l1_data_gas_price_wei: GasPrice(context_config.min_l1_data_gas_price_wei), - max_l1_data_gas_price_wei: GasPrice(context_config.max_l1_data_gas_price_wei), - l1_data_gas_price_multiplier: Ratio::new( - context_config.l1_data_gas_price_multiplier_ppt, - 1000, - ), - l1_gas_tip_wei: GasPrice(context_config.l1_gas_tip_wei), - }; + let gas_price_params = make_gas_price_params(&context_config); let cancel_token = CancellationToken::new(); ( @@ -123,12 +117,14 @@ async fn validate_empty_proposal() { let (proposal_args, mut content_sender) = create_proposal_validate_arguments(); // Send an empty proposal. content_sender - .send(ProposalPart::Fin(ProposalFin { proposal_commitment: BlockHash::default() })) + .send(ProposalPart::Fin(ProposalFin { + proposal_commitment: ConsensusProposalCommitment::default(), + })) .await .unwrap(); let res = validate_proposal(proposal_args.into()).await; - assert_matches!(res, Ok(val) if val == BlockHash::default()); + assert_matches!(res, Ok(val) if val == ConsensusProposalCommitment::default()); } #[tokio::test] @@ -136,7 +132,7 @@ async fn validate_proposal_success() { let (mut proposal_args, mut content_sender) = create_proposal_validate_arguments(); let n_executed = 1; // Setup deps to validate the block. - proposal_args.deps.setup_deps_for_validate(BlockNumber(0), n_executed); + proposal_args.deps.setup_deps_for_validate(BlockNumber(0), n_executed, 1); // Send a valid block info. let block_info = block_info(BlockNumber(0)); content_sender.send(ProposalPart::BlockInfo(block_info)).await.unwrap(); @@ -150,12 +146,14 @@ async fn validate_proposal_success() { .await .unwrap(); content_sender - .send(ProposalPart::Fin(ProposalFin { proposal_commitment: BlockHash::default() })) + .send(ProposalPart::Fin(ProposalFin { + proposal_commitment: ConsensusProposalCommitment::default(), + })) .await .unwrap(); let res = validate_proposal(proposal_args.into()).await; - assert_matches!(res, Ok(val) if val == BlockHash::default()); + assert_matches!(res, Ok(val) if val == ConsensusProposalCommitment::default()); } #[tokio::test] @@ -264,7 +262,9 @@ async fn receive_fin_without_executed_transaction_count() { content_sender.send(ProposalPart::BlockInfo(block_info)).await.unwrap(); // Send Fin part without sending executed transaction count. content_sender - .send(ProposalPart::Fin(ProposalFin { proposal_commitment: BlockHash::default() })) + .send(ProposalPart::Fin(ProposalFin { + proposal_commitment: ConsensusProposalCommitment::default(), + })) .await .unwrap(); @@ -334,7 +334,7 @@ async fn proposal_fin_mismatch() { .await .unwrap(); // Send Fin part. - let received_fin = BlockHash::default(); + let received_fin = ConsensusProposalCommitment::default(); content_sender .send(ProposalPart::Fin(ProposalFin { proposal_commitment: received_fin })) .await @@ -372,7 +372,9 @@ async fn batcher_returns_invalid_proposal() { .await .unwrap(); content_sender - .send(ProposalPart::Fin(ProposalFin { proposal_commitment: BlockHash::default() })) + .send(ProposalPart::Fin(ProposalFin { + proposal_commitment: ConsensusProposalCommitment::default(), + })) .await .unwrap(); diff --git a/crates/apollo_consensus_orchestrator_config/src/config.rs b/crates/apollo_consensus_orchestrator_config/src/config.rs index 6c1b81134fd..b30a687058e 100644 --- a/crates/apollo_consensus_orchestrator_config/src/config.rs +++ b/crates/apollo_consensus_orchestrator_config/src/config.rs @@ -3,8 +3,10 @@ use std::fmt::Debug; use std::time::Duration; use apollo_config::converters::{ + deserialize_comma_separated_str, deserialize_milliseconds_to_duration, deserialize_seconds_to_duration, + serialize_optional_comma_separated, }; use apollo_config::dumping::{ser_optional_param, ser_param, SerializeConfig}; use apollo_config::{ParamPath, ParamPrivacyInput, SerializedParam}; @@ -93,6 +95,10 @@ pub struct ContextConfig { pub proposal_buffer_size: usize, /// The number of validators. pub num_validators: u64, + /// Optional explicit set of validator IDs (contract addresses) to use. + /// If provided, this overrides `num_validators`. + #[serde(default, deserialize_with = "deserialize_comma_separated_str")] + pub validator_ids: Option>, /// The chain id of the Starknet chain. pub chain_id: ChainId, /// Maximum allowed deviation (seconds) of a proposed block's timestamp from the current time. @@ -126,13 +132,19 @@ pub struct ContextConfig { pub l1_data_gas_price_multiplier_ppt: u128, /// This additional gas is added to the L1 gas price. pub l1_gas_tip_wei: u128, - /// If true, sets STRK gas price to its minimum price from the versioned constants. - pub constant_l2_gas_price: bool, + /// If given, will override the L2 gas price. + pub override_l2_gas_price_fri: Option, + /// If given, will override the L1 gas price. + pub override_l1_gas_price_wei: Option, + /// If given, will override the L1 data gas price. + pub override_l1_data_gas_price_wei: Option, + /// If given, will override the conversion rate. + pub override_eth_to_fri_rate: Option, } impl SerializeConfig for ContextConfig { fn dump(&self) -> BTreeMap { - BTreeMap::from_iter([ + let mut dump = BTreeMap::from_iter([ ser_param( "proposal_buffer_size", &self.proposal_buffer_size, @@ -221,13 +233,43 @@ impl SerializeConfig for ContextConfig { "This additional gas is added to the L1 gas price.", ParamPrivacyInput::Public, ), - ser_param( - "constant_l2_gas_price", - &self.constant_l2_gas_price, - "If true, sets STRK gas price to its minimum price from the versioned constants.", - ParamPrivacyInput::Public, - ), - ]) + ]); + dump.extend(ser_optional_param( + &self.override_l2_gas_price_fri, + 0, + "override_l2_gas_price_fri", + "Replace the L2 gas price (fri) with this value.", + ParamPrivacyInput::Public, + )); + dump.extend(ser_optional_param( + &self.override_l1_gas_price_wei, + 0, + "override_l1_gas_price_wei", + "Replace the L1 gas price (wei) with this value.", + ParamPrivacyInput::Public, + )); + dump.extend(ser_optional_param( + &self.override_l1_data_gas_price_wei, + 0, + "override_l1_data_gas_price_wei", + "Replace the L1 data gas price (wei) with this value.", + ParamPrivacyInput::Public, + )); + dump.extend(ser_optional_param( + &self.override_eth_to_fri_rate, + 0, + "override_eth_to_fri_rate", + "Replace the Eth-to-Fri conversion rate with this value.", + ParamPrivacyInput::Public, + )); + dump.extend(ser_optional_param( + &serialize_optional_comma_separated(&self.validator_ids), + "".to_string(), + "validator_ids", + "Optional explicit set of validator IDs (comma separated).", + ParamPrivacyInput::Public, + )); + dump } } @@ -236,6 +278,7 @@ impl Default for ContextConfig { Self { proposal_buffer_size: 100, num_validators: 1, + validator_ids: None, chain_id: ChainId::Mainnet, block_timestamp_window_seconds: 1, l1_da_mode: true, @@ -248,7 +291,10 @@ impl Default for ContextConfig { max_l1_data_gas_price_wei: ETH_FACTOR, l1_data_gas_price_multiplier_ppt: 135, l1_gas_tip_wei: GWEI_FACTOR, - constant_l2_gas_price: false, + override_l2_gas_price_fri: None, + override_l1_gas_price_wei: None, + override_l1_data_gas_price_wei: None, + override_eth_to_fri_rate: None, } } } diff --git a/crates/apollo_dashboard/Cargo.toml b/crates/apollo_dashboard/Cargo.toml index b68cf9063a2..0fa416759e3 100644 --- a/crates/apollo_dashboard/Cargo.toml +++ b/crates/apollo_dashboard/Cargo.toml @@ -59,3 +59,4 @@ apollo_l1_provider = { workspace = true, features = ["testing"] } apollo_mempool = { workspace = true, features = ["testing"] } apollo_mempool_p2p = { workspace = true, features = ["testing"] } apollo_state_sync_metrics = { workspace = true, features = ["testing"] } +rstest.workspace = true diff --git a/crates/apollo_dashboard/resources/dev_grafana.json b/crates/apollo_dashboard/resources/dev_grafana.json index a481662798b..74e1c5ce3e7 100644 --- a/crates/apollo_dashboard/resources/dev_grafana.json +++ b/crates/apollo_dashboard/resources/dev_grafana.json @@ -3,23 +3,26 @@ "Overview": { "panels": [ { - "title": "Consensus Round", - "description": "The round the node is currently working on", + "title": "Average Block Time", + "description": "Average block time (1m window)", "type": "timeseries", "exprs": [ - "consensus_round{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + "1 / rate(consensus_block_number{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1m])" ], - "extra_params": {} + "extra_params": { + "unit": "s" + } }, { - "title": "Write Blob Failure by Reason", - "description": "The number of failed blob writes to Cende (10m window)", + "title": "Consensus Round", + "description": "The round the node is currently working on", "type": "timeseries", "exprs": [ - "sum by (cende_write_failure_reason) (increase(cende_write_blob_failure{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m]))" + "consensus_round{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], "extra_params": { - "log_query": "\"CENDE_FAILURE\"" + "log_query": "\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" } }, { @@ -38,27 +41,27 @@ "exprs": [ "rate(batcher_batched_transactions{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"BATCHER_FIN_VALIDATOR\"" + } }, { - "title": "Sync Diff From Central", - "description": "The number of blocks that were not fully synced yet", + "title": "Consensus Height Diff From Sync", + "description": "The difference between the consensus height and the sync height", "type": "timeseries", "exprs": [ - "apollo_central_sync_central_block_marker{cluster=~\"$cluster\", namespace=~\"$namespace\"} - apollo_state_sync_class_manager_marker{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + "(consensus_block_number{cluster=~\"$cluster\", namespace=~\"$namespace\"} - apollo_state_sync_class_manager_marker{cluster=~\"$cluster\", namespace=~\"$namespace\"})" ], "extra_params": {} }, { - "title": "Transaction Failure Rate by Type", - "description": "The rate of failed transactions vs received transactions by type (over the selected time range)", + "title": "Transactions Failed by Reason", + "description": "The number of transactions failed by reason (over the selected time range)", "type": "stat", "exprs": [ - "(sum by (tx_type) (increase(gateway_transactions_failed{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) / sum by (tx_type) (increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])))" + "sum by (add_tx_failure_reason) (increase(gateway_add_tx_failure{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) > 0" ], - "extra_params": { - "unit": "percentunit" - } + "extra_params": {} } ], "collapsed": false @@ -72,7 +75,10 @@ "exprs": [ "consensus_block_number{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "log_query": "\"START_HEIGHT: running consensus for height\" OR \"Start building proposal\" OR \"Start validating proposal\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "Consensus Round", @@ -81,7 +87,45 @@ "exprs": [ "consensus_round{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "log_query": "\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } + }, + { + "title": "Consensus Round Advanced", + "description": "The number of times the consensus round advanced (counter is increased whenever round > 0) (10m window)", + "type": "timeseries", + "exprs": [ + "increase(consensus_round_advances{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" + ], + "extra_params": { + "log_query": "\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } + }, + { + "title": "Average Block Time", + "description": "Average block time (1m window)", + "type": "timeseries", + "exprs": [ + "1 / rate(consensus_block_number{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1m])" + ], + "extra_params": { + "unit": "s" + } + }, + { + "title": "Consensus Round Above Zero", + "description": "Occurances where the consensus round was 1, relative to displayed range", + "type": "timeseries", + "exprs": [ + "consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"} - (consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"} @ start())" + ], + "extra_params": { + "log_query": "\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "Consensus Height Diff From Sync", @@ -93,14 +137,15 @@ "extra_params": {} }, { - "title": "Average Block Time", - "description": "Average block time (10m window)", + "title": "Consensus Decisions Reached As Proposer", + "description": "The number of rounds with decision reached where this node is the proposer (10m window)", "type": "timeseries", "exprs": [ - "1 / rate(consensus_block_number{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" + "increase(consensus_decisions_reached_as_proposer{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" ], "extra_params": { - "unit": "s" + "log_query": "\"Building proposal\" OR \"BATCHER_FIN_PROPOSER\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" } }, { @@ -110,7 +155,10 @@ "exprs": [ "increase(consensus_decisions_reached_by_consensus{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"DECISION_REACHED: Decision reached for round\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "Decisions Reached By Sync", @@ -119,7 +167,10 @@ "exprs": [ "increase(consensus_decisions_reached_by_sync{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"Decision learned via sync protocol.\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "Proposal Build: Number of Proposals Started", @@ -147,7 +198,8 @@ "sum by (build_proposal_failure_reason) (increase(consensus_build_proposal_failure{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) > 0" ], "extra_params": { - "log_query": "\"PROPOSAL_FAILED: Proposal failed as proposer\"" + "log_query": "\"PROPOSAL_FAILED: Proposal failed as proposer\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" } }, { @@ -166,7 +218,10 @@ "exprs": [ "increase(consensus_proposals_validated{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"Validated proposal.\" OR \"PROPOSAL_FAILED\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "Proposal Validation: Number of Invalid Proposals", @@ -175,7 +230,10 @@ "exprs": [ "increase(consensus_proposals_invalid{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"Validated proposal.\" OR \"PROPOSAL_FAILED\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "Proposal Validation: Proposal Failure by Reason", @@ -185,7 +243,8 @@ "sum by (validate_proposal_failure_reason) (increase(consensus_validate_proposal_failure{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) > 0" ], "extra_params": { - "log_query": "\"PROPOSAL_FAILED: Proposal failed as validator\"" + "log_query": "\"PROPOSAL_FAILED: Proposal failed as validator\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" } }, { @@ -195,7 +254,10 @@ "exprs": [ "sum by (timeout_type) (increase(consensus_timeouts{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m]))" ], - "extra_params": {} + "extra_params": { + "log_query": "\"Applying Timeout\"", + "log_comment": "-- \"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \"Broadcasting\"" + } }, { "title": "L2 Gas Price (GFri)", @@ -263,7 +325,9 @@ "exprs": [ "increase(batcher_preconfirmed_block_written{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"write_pre_confirmed_block request succeeded.\"" + } } ], "collapsed": true @@ -277,7 +341,9 @@ "exprs": [ "batcher_storage_height{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "log_query": "\"Committing block at height\"" + } }, { "title": "Batched Transactions Rate (TPS)", @@ -286,7 +352,9 @@ "exprs": [ "rate(batcher_batched_transactions{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1m])" ], - "extra_params": {} + "extra_params": { + "log_query": "\"BATCHER_FIN_VALIDATOR\"" + } }, { "title": "Proposal Build: Deferred TXs", @@ -329,7 +397,7 @@ { "title": "Block Close Reasons", "description": "Number of blocks closed by reason (10m window)", - "type": "timeseries", + "type": "stat", "exprs": [ "sum by (block_close_reason) (increase(batcher_block_close_reason{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m]))" ], @@ -353,7 +421,9 @@ "exprs": [ "consensus_num_txs_in_proposal{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "log_query": "\"BATCHER_FIN_PROPOSER\"" + } } ], "collapsed": true @@ -505,7 +575,7 @@ "description": "The number of transactions received by source (over the selected time range)", "type": "stat", "exprs": [ - "sum by (source) (increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) " + "sum by (source) (increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range]))" ], "extra_params": { "log_query": "\"Processing tx\" AND \"is_p2p=\"" @@ -516,7 +586,7 @@ "description": "The number of transactions received by type (over the selected time range)", "type": "stat", "exprs": [ - "sum by (tx_type) (increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) " + "sum by (tx_type) (increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range]))" ], "extra_params": { "log_query": "\"Processing tx\"" @@ -547,22 +617,24 @@ "description": "The number of transactions sent to mempool by type (over the selected time range)", "type": "stat", "exprs": [ - "sum by (tx_type) (increase(gateway_transactions_sent_to_mempool{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range]))" + "sum by (tx_type) (increase(gateway_transactions_sent_to_mempool{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range]))" ], "extra_params": {} }, { - "title": "gateway_validate_stateful_tx_storage_micros", - "description": "Total time spent in storage operations in micros during stateful tx validation", + "title": "Gateway Validate Stateful Tx Storage Access Time", + "description": "Total time spent in storage operations during stateful tx validation", "type": "timeseries", "exprs": [ - "histogram_quantile(0.50, sum by (le) (rate(gateway_validate_stateful_tx_storage_micros_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))", - "histogram_quantile(0.95, sum by (le) (rate(gateway_validate_stateful_tx_storage_micros_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))" + "histogram_quantile(0.50, sum by (le) (rate(gateway_validate_stateful_tx_storage_time_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))", + "histogram_quantile(0.95, sum by (le) (rate(gateway_validate_stateful_tx_storage_time_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))" ], - "extra_params": {} + "extra_params": { + "unit": "s" + } }, { - "title": "gateway_validate_stateful_tx_storage_operations", + "title": "Gateway Validate Stateful Tx Storage Operations", "description": "Total number of storage operations during stateful tx validation", "type": "timeseries", "exprs": [ @@ -601,7 +673,7 @@ "description": "Number of transactions dropped from the mempool by reason (over the selected time range)", "type": "stat", "exprs": [ - "sum by (drop_reason) (increase(mempool_transactions_dropped{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range]))" + "sum by (drop_reason) (increase(mempool_transactions_dropped{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range]))" ], "extra_params": {} }, @@ -682,13 +754,16 @@ "L1 Provider": { "panels": [ { - "title": "l1_message_scraper_seconds_since_last_successful_scrape", - "description": "Number of seconds since the last successful scrape of the L1 message scraper", + "title": "Seconds since last successful l1 event scrape", + "description": "The number of seconds since the last successful scrape of the L1 message scraper (assuming there was a scrape in the last 12 hours)", "type": "timeseries", "exprs": [ - "l1_message_scraper_seconds_since_last_successful_scrape{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + "time() - max_over_time((timestamp(increase(l1_message_scraper_success_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}[12h])) != 0))[12h:])" ], - "extra_params": {} + "extra_params": { + "unit": "s", + "log_query": "\"BaseLayerError during scraping\"" + } }, { "title": "L1 Message Scraper Success Count", @@ -722,6 +797,28 @@ }, "ETH→STRK Rate & L1 Gas Price": { "panels": [ + { + "title": "Seconds since last successful L1 gas price scrape", + "description": "The number of seconds since the last successful scrape of the L1 gas price scraper (assuming there was a scrape in the last 12 hours)", + "type": "timeseries", + "exprs": [ + "time() - max_over_time((timestamp(increase(l1_gas_price_scraper_success_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}[12h])) != 0))[12h:])" + ], + "extra_params": { + "unit": "s" + } + }, + { + "title": "Seconds since last successful ETH→STRK rate update", + "description": "The number of seconds since the last successful ETH→STRK rate update (assuming there was an update in the last 12 hours)", + "type": "timeseries", + "exprs": [ + "time() - max_over_time((timestamp(increase(eth_to_strk_success_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}[12h])) != 0))[12h:])" + ], + "extra_params": { + "unit": "s" + } + }, { "title": "ETH→STRK Rate Query Success (binary)", "description": "Indicates whether the ETH→STRK rate query succeeded (1m window) \nExpected to be 1 every 15 minutes.", @@ -743,7 +840,7 @@ "extra_params": {} }, { - "title": "eth_to_strk_rate", + "title": "ETH→STRK rate", "description": "ETH→STRK rate (divided by DEFAULT_ETH_TO_FRI_RATE=1000000000000000000000)", "type": "timeseries", "exprs": [ @@ -864,6 +961,34 @@ "extra_params": { "unit": "percentunit" } + }, + { + "title": "Transactions Per Block", + "description": "The number of transactions per block", + "type": "timeseries", + "exprs": [ + "histogram_quantile(0.50, sum by (le) (rate(batcher_num_transaction_in_block_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))", + "histogram_quantile(0.95, sum by (le) (rate(batcher_num_transaction_in_block_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))" + ], + "extra_params": {} + }, + { + "title": "Average Sierra Gas Usage in Block", + "description": "The average sierra gas usage in block (10m window)", + "type": "timeseries", + "exprs": [ + "avg_over_time(batcher_sierra_gas_in_last_block{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])/1000000000" + ], + "extra_params": {} + }, + { + "title": "Average Proving Gas Usage in Block", + "description": "The average proving gas usage in block (10m window)", + "type": "timeseries", + "exprs": [ + "avg_over_time(batcher_proving_gas_in_last_block{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])/1000000000" + ], + "extra_params": {} } ], "collapsed": true @@ -884,7 +1009,7 @@ }, { "title": "Number of Classes", - "description": "Number of classes, labeled by type (regular, deprecated)", + "description": "Number of classes, labeled by type (regular, deprecated) (10m window)", "type": "stat", "exprs": [ "sum by (class_type) (increase(class_manager_n_classes{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m]))" @@ -900,7 +1025,11 @@ "histogram_quantile(0.95, sum by (le, class_object_type) (rate(class_manager_class_sizes_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))" ], "extra_params": { - "unit": "decmbytes" + "unit": "decmbytes", + "legends": [ + "0.50 {{class_object_type}}", + "0.95 {{class_object_type}}" + ] } } ], @@ -911,7 +1040,7 @@ { "title": "Number of Connected Peers", "description": "The number of connected peers in Consensus P2P", - "type": "timeseries", + "type": "stat", "exprs": [ "apollo_consensus_num_connected_peers{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], @@ -919,105 +1048,156 @@ }, { "title": "Consensus Votes Number of Sent Messages", - "description": "The increase in the number of vote messages sent by consensus p2p (10m window)", - "type": "timeseries", + "description": "The increase in the number of vote messages sent by consensus p2p (over the selected time range)", + "type": "stat", "exprs": [ - "increase(apollo_consensus_votes_num_sent_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" + "increase(apollo_consensus_votes_num_sent_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])" ], "extra_params": {} }, { "title": "Consensus Votes Number of Received Messages", - "description": "The increase in the number of vote messages received by consensus p2p (10m window)", - "type": "timeseries", + "description": "The increase in the number of vote messages received by consensus p2p (over the selected time range)", + "type": "stat", "exprs": [ - "increase(apollo_consensus_votes_num_received_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" + "increase(apollo_consensus_votes_num_received_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])" ], "extra_params": {} }, { - "title": "apollo_consensus_votes_num_dropped_messages", - "description": "The number of messages dropped by the consensus p2p component over the Votes topic", - "type": "timeseries", + "title": "Consensus Votes Dropped Messages By Reason", + "description": "The number of dropped consensus votes messages, by reason (over the selected time range)", + "type": "stat", "exprs": [ - "sum by (drop_reason) (apollo_consensus_votes_num_dropped_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"})" + "sum by (drop_reason) (increase(apollo_consensus_votes_num_dropped_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) > 0" ], "extra_params": {} }, { "title": "Consensus Conflicting Votes", - "description": "The increase in the number of conflicting votes (12h window)", - "type": "timeseries", + "description": "The increase in the number of conflicting votes (over the selected time range)", + "type": "stat", "exprs": [ - "increase(consensus_conflicting_votes{cluster=~\"$cluster\", namespace=~\"$namespace\"}[12h])" + "increase(consensus_conflicting_votes{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])" ], "extra_params": {} }, { "title": "Consensus Proposals Number of Sent Messages", - "description": "The increase in the number of proposal messages sent by consensus p2p (10m window)", - "type": "timeseries", + "description": "The increase in the number of proposal messages sent by consensus p2p (over the selected time range)", + "type": "stat", "exprs": [ - "increase(apollo_consensus_proposals_num_sent_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" + "increase(apollo_consensus_proposals_num_sent_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])" ], "extra_params": {} }, { "title": "Consensus Proposals Number of Received Messages", - "description": "The increase in the number of proposal messages received by consensus p2p (10m window)", - "type": "timeseries", + "description": "The increase in the number of proposal messages received by consensus p2p (over the selected time range)", + "type": "stat", "exprs": [ - "increase(apollo_consensus_proposals_num_received_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])" + "increase(apollo_consensus_proposals_num_received_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])" ], "extra_params": {} }, { - "title": "apollo_consensus_proposals_num_dropped_messages", - "description": "The number of messages dropped by the consensus p2p component over the Proposals topic", - "type": "timeseries", + "title": "Consensus Proposals Dropped Messages By Reason", + "description": "The number of dropped consensus proposals messages, by reason (over the selected time range)", + "type": "stat", "exprs": [ - "sum by (drop_reason) (apollo_consensus_proposals_num_dropped_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"})" + "sum by (drop_reason) (increase(apollo_consensus_proposals_num_dropped_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) > 0" ], "extra_params": {} }, { - "title": "apollo_consensus_network_events", - "description": "Network events counter by event type for consensus", - "type": "timeseries", + "title": "Consensus Network Events By Type", + "description": "Network events received by consensus p2p, by event type (over the selected time range)", + "type": "stat", "exprs": [ - "sum by (event_type) (apollo_consensus_network_events{cluster=~\"$cluster\", namespace=~\"$namespace\"})" + "sum by (event_type) (increase(apollo_consensus_network_events{cluster=~\"$cluster\", namespace=~\"$namespace\"}[$__range])) > 0" ], "extra_params": {} } ], "collapsed": true }, - "StateSyncP2p": { + "MempoolP2p": { "panels": [ { - "title": "apollo_p2p_sync_num_connected_peers", - "description": "The number of connected peers to the p2p sync component", - "type": "stat", + "title": "apollo_mempool_p2p_num_connected_peers", + "description": "The number of connected peers to the mempool p2p component", + "type": "timeseries", + "exprs": [ + "apollo_mempool_p2p_num_connected_peers{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + ], + "extra_params": {} + }, + { + "title": "apollo_mempool_p2p_num_sent_messages", + "description": "The number of messages sent by the mempool p2p component", + "type": "timeseries", "exprs": [ - "apollo_p2p_sync_num_connected_peers{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + "apollo_mempool_p2p_num_sent_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], "extra_params": {} }, { - "title": "apollo_p2p_sync_num_active_inbound_sessions", - "description": "The number of inbound sessions to the p2p sync component", + "title": "apollo_mempool_p2p_num_received_messages", + "description": "The number of messages received by the mempool p2p component", + "type": "timeseries", + "exprs": [ + "apollo_mempool_p2p_num_received_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + ], + "extra_params": {} + }, + { + "title": "Mempool P2p Broadcasted Transaction Batch Size", + "description": "The number of transactions in batches broadcast by the mempool p2p component", + "type": "timeseries", + "exprs": [ + "histogram_quantile(0.50, sum by (le) (rate(apollo_mempool_p2p_broadcasted_transaction_batch_size_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))", + "histogram_quantile(0.95, sum by (le) (rate(apollo_mempool_p2p_broadcasted_transaction_batch_size_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))" + ], + "extra_params": {} + }, + { + "title": "apollo_mempool_p2p_network_events", + "description": "Network events counter by event type for mempool p2p", + "type": "timeseries", + "exprs": [ + "sum by (event_type) (apollo_mempool_p2p_network_events{cluster=~\"$cluster\", namespace=~\"$namespace\"})" + ], + "extra_params": {} + }, + { + "title": "apollo_mempool_p2p_num_dropped_messages", + "description": "The number of messages dropped by the mempool p2p component", + "type": "timeseries", + "exprs": [ + "sum by (drop_reason) (apollo_mempool_p2p_num_dropped_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"})" + ], + "extra_params": {} + } + ], + "collapsed": true + }, + "Reverts": { + "panels": [ + { + "title": "apollo_consensus_reverted_batcher_up_to_and_including", + "description": "The block number up to which the batcher has reverted", "type": "stat", "exprs": [ - "apollo_p2p_sync_num_active_inbound_sessions{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + "apollo_consensus_reverted_batcher_up_to_and_including{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], "extra_params": {} }, { - "title": "apollo_p2p_sync_num_active_outbound_sessions", - "description": "The number of outbound sessions to the p2p sync component", + "title": "apollo_state_sync_reverted_up_to_and_including", + "description": "The block number up to which the state sync has reverted", "type": "stat", "exprs": [ - "apollo_p2p_sync_num_active_outbound_sessions{cluster=~\"$cluster\", namespace=~\"$namespace\"}" + "apollo_state_sync_reverted_up_to_and_including{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], "extra_params": {} } @@ -1080,66 +1260,6 @@ ], "collapsed": true }, - "MempoolP2p": { - "panels": [ - { - "title": "apollo_mempool_p2p_num_connected_peers", - "description": "The number of connected peers to the mempool p2p component", - "type": "timeseries", - "exprs": [ - "apollo_mempool_p2p_num_connected_peers{cluster=~\"$cluster\", namespace=~\"$namespace\"}" - ], - "extra_params": {} - }, - { - "title": "apollo_mempool_p2p_num_sent_messages", - "description": "The number of messages sent by the mempool p2p component", - "type": "timeseries", - "exprs": [ - "apollo_mempool_p2p_num_sent_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}" - ], - "extra_params": {} - }, - { - "title": "apollo_mempool_p2p_num_received_messages", - "description": "The number of messages received by the mempool p2p component", - "type": "timeseries", - "exprs": [ - "apollo_mempool_p2p_num_received_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"}" - ], - "extra_params": {} - }, - { - "title": "apollo_mempool_p2p_broadcasted_transaction_batch_size", - "description": "The number of transactions in batches broadcast by the mempool p2p component", - "type": "timeseries", - "exprs": [ - "histogram_quantile(0.50, sum by (le) (rate(apollo_mempool_p2p_broadcasted_transaction_batch_size_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))", - "histogram_quantile(0.95, sum by (le) (rate(apollo_mempool_p2p_broadcasted_transaction_batch_size_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\"}[5m])))" - ], - "extra_params": {} - }, - { - "title": "apollo_mempool_p2p_network_events", - "description": "Network events counter by event type for mempool p2p", - "type": "timeseries", - "exprs": [ - "sum by (event_type) (apollo_mempool_p2p_network_events{cluster=~\"$cluster\", namespace=~\"$namespace\"})" - ], - "extra_params": {} - }, - { - "title": "apollo_mempool_p2p_num_dropped_messages", - "description": "The number of messages dropped by the mempool p2p component", - "type": "timeseries", - "exprs": [ - "sum by (drop_reason) (apollo_mempool_p2p_num_dropped_messages{cluster=~\"$cluster\", namespace=~\"$namespace\"})" - ], - "extra_params": {} - } - ], - "collapsed": true - }, "Tokio Runtime Metrics": { "panels": [ { @@ -1149,7 +1269,11 @@ "exprs": [ "tokio_total_busy_duration{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_min_busy_duration", @@ -1158,7 +1282,11 @@ "exprs": [ "tokio_min_busy_duration{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_max_busy_duration", @@ -1167,7 +1295,11 @@ "exprs": [ "tokio_max_busy_duration{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_total_park_count", @@ -1176,7 +1308,11 @@ "exprs": [ "tokio_total_park_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_min_park_count", @@ -1185,7 +1321,11 @@ "exprs": [ "tokio_min_park_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_max_park_count", @@ -1194,7 +1334,11 @@ "exprs": [ "tokio_max_park_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_global_queue_depth", @@ -1203,7 +1347,11 @@ "exprs": [ "tokio_global_queue_depth{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } }, { "title": "tokio_workers_count", @@ -1212,7 +1360,11 @@ "exprs": [ "tokio_workers_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}" ], - "extra_params": {} + "extra_params": { + "legends": [ + "{{pod}}" + ] + } } ], "collapsed": true diff --git a/crates/apollo_dashboard/resources/dev_grafana_alerts_mainnet.json b/crates/apollo_dashboard/resources/dev_grafana_alerts_mainnet.json index 7d57f24d871..a5fe4267f54 100644 --- a/crates/apollo_dashboard/resources/dev_grafana_alerts_mainnet.json +++ b/crates/apollo_dashboard/resources/dev_grafana_alerts_mainnet.json @@ -252,6 +252,34 @@ "severity": "p4", "observer_applicable": "true" }, + { + "name": "consensus_round_above_zero", + "title": "Consensus round above zero", + "ruleGroup": "consensus", + "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1h])", + "conditions": [ + { + "evaluator": { + "params": [ + 0.0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "for": "30s", + "intervalSec": 30, + "severity": "p5", + "observer_applicable": "false" + }, { "name": "consensus_votes_num_sent_messages", "title": "Consensus votes num sent messages", @@ -309,8 +337,8 @@ "observer_applicable": "false" }, { - "name": "gateway_add_tx_idle_all_sources", - "title": "Gateway add_tx idle (all sources)", + "name": "gateway_add_tx_idle_p2p_rpc", + "title": "Gateway add_tx idle (p2p+rpc)", "ruleGroup": "gateway", "expr": "sum(increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[120s])) or vector(0)", "conditions": [ @@ -333,7 +361,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p1", + "severity": "p2", "observer_applicable": "false" }, { @@ -501,7 +529,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p1", + "severity": "p2", "observer_applicable": "false" }, { @@ -617,8 +645,8 @@ "observer_applicable": "false" }, { - "name": "mempool_add_tx_idle_all_sources", - "title": "Mempool add_tx idle (all sources)", + "name": "mempool_add_tx_idle_p2p_rpc", + "title": "Mempool add_tx idle (p2p+rpc)", "ruleGroup": "mempool", "expr": "sum(increase(mempool_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[120s])) or vector(0)", "conditions": [ @@ -793,7 +821,7 @@ { "evaluator": { "params": [ - 0.3 + 0.5 ], "type": "gt" }, @@ -849,7 +877,7 @@ { "evaluator": { "params": [ - 10.0 + 6.666666666666667 ], "type": "lt" }, @@ -865,7 +893,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p1", + "severity": "p2", "observer_applicable": "true" }, { @@ -977,47 +1005,19 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p1", + "severity": "p2", "observer_applicable": "true" }, - { - "name": "consensus_round_above_zero", - "title": "Consensus round above zero", - "ruleGroup": "consensus", - "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1h])", - "conditions": [ - { - "evaluator": { - "params": [ - 0.0 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "reducer": { - "params": [], - "type": "avg" - }, - "type": "query" - } - ], - "for": "30s", - "intervalSec": 30, - "severity": "p4", - "observer_applicable": "false" - }, { "name": "consensus_round_above_zero_multiple_times", "title": "Consensus round above zero multiple times", "ruleGroup": "consensus", - "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[30m])", + "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])", "conditions": [ { "evaluator": { "params": [ - 5.0 + 20.0 ], "type": "gt" }, @@ -1045,7 +1045,7 @@ { "evaluator": { "params": [ - 20.0 + 13.333333333333334 ], "type": "gt" }, @@ -1313,7 +1313,35 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p2", + "severity": "p4", + "observer_applicable": "false" + }, + { + "name": "high_empty_blocks_ratio", + "title": "High ratio of empty blocks", + "ruleGroup": "batcher", + "expr": "sum(increase(batcher_num_transaction_in_block_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", le=\"0.001\"}[120s])) / clamp_min(sum(increase(batcher_num_transaction_in_block_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}[120s])), 1)", + "conditions": [ + { + "evaluator": { + "params": [ + 0.3 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "for": "30s", + "intervalSec": 30, + "severity": "p1", "observer_applicable": "false" }, { @@ -1465,7 +1493,7 @@ { "evaluator": { "params": [ - 2000.0 + 10000.0 ], "type": "gt" }, @@ -1481,7 +1509,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p2", + "severity": "p3", "observer_applicable": "false" }, { @@ -1537,7 +1565,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p2", + "severity": "p1", "observer_applicable": "false" }, { @@ -1566,7 +1594,7 @@ "for": "30s", "intervalSec": 30, "severity": "p2", - "observer_applicable": "true" + "observer_applicable": "false" }, { "name": "state_sync_stuck", diff --git a/crates/apollo_dashboard/resources/dev_grafana_alerts_testnet.json b/crates/apollo_dashboard/resources/dev_grafana_alerts_testnet.json index 2451808dcb2..a46529b6d2d 100644 --- a/crates/apollo_dashboard/resources/dev_grafana_alerts_testnet.json +++ b/crates/apollo_dashboard/resources/dev_grafana_alerts_testnet.json @@ -252,6 +252,34 @@ "severity": "p4", "observer_applicable": "true" }, + { + "name": "consensus_round_above_zero", + "title": "Consensus round above zero", + "ruleGroup": "consensus", + "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1h])", + "conditions": [ + { + "evaluator": { + "params": [ + 0.0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "for": "30s", + "intervalSec": 30, + "severity": "p5", + "observer_applicable": "false" + }, { "name": "consensus_votes_num_sent_messages", "title": "Consensus votes num sent messages", @@ -309,8 +337,8 @@ "observer_applicable": "false" }, { - "name": "gateway_add_tx_idle_all_sources", - "title": "Gateway add_tx idle (all sources)", + "name": "gateway_add_tx_idle_p2p_rpc", + "title": "Gateway add_tx idle (p2p+rpc)", "ruleGroup": "gateway", "expr": "sum(increase(gateway_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[120s])) or vector(0)", "conditions": [ @@ -333,7 +361,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p1", + "severity": "p2", "observer_applicable": "false" }, { @@ -501,7 +529,7 @@ ], "for": "30s", "intervalSec": 30, - "severity": "p1", + "severity": "p2", "observer_applicable": "false" }, { @@ -617,8 +645,8 @@ "observer_applicable": "false" }, { - "name": "mempool_add_tx_idle_all_sources", - "title": "Mempool add_tx idle (all sources)", + "name": "mempool_add_tx_idle_p2p_rpc", + "title": "Mempool add_tx idle (p2p+rpc)", "ruleGroup": "mempool", "expr": "sum(increase(mempool_transactions_received{cluster=~\"$cluster\", namespace=~\"$namespace\"}[120s])) or vector(0)", "conditions": [ @@ -793,7 +821,7 @@ { "evaluator": { "params": [ - 0.3 + 0.5 ], "type": "gt" }, @@ -877,7 +905,7 @@ { "evaluator": { "params": [ - 10.0 + 6.666666666666667 ], "type": "lt" }, @@ -1064,44 +1092,16 @@ "severity": "p4", "observer_applicable": "true" }, - { - "name": "consensus_round_above_zero", - "title": "Consensus round above zero", - "ruleGroup": "consensus", - "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[1h])", - "conditions": [ - { - "evaluator": { - "params": [ - 0.0 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "reducer": { - "params": [], - "type": "avg" - }, - "type": "query" - } - ], - "for": "30s", - "intervalSec": 30, - "severity": "p4", - "observer_applicable": "false" - }, { "name": "consensus_round_above_zero_multiple_times", "title": "Consensus round above zero multiple times", "ruleGroup": "consensus", - "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[30m])", + "expr": "increase(consensus_round_above_zero{cluster=~\"$cluster\", namespace=~\"$namespace\"}[10m])", "conditions": [ { "evaluator": { "params": [ - 5.0 + 20.0 ], "type": "gt" }, @@ -1129,7 +1129,7 @@ { "evaluator": { "params": [ - 20.0 + 13.333333333333334 ], "type": "gt" }, @@ -1400,6 +1400,34 @@ "severity": "p4", "observer_applicable": "false" }, + { + "name": "high_empty_blocks_ratio", + "title": "High ratio of empty blocks", + "ruleGroup": "batcher", + "expr": "sum(increase(batcher_num_transaction_in_block_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", le=\"0.001\"}[300s])) / clamp_min(sum(increase(batcher_num_transaction_in_block_count{cluster=~\"$cluster\", namespace=~\"$namespace\"}[300s])), 1)", + "conditions": [ + { + "evaluator": { + "params": [ + 0.6 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "for": "30s", + "intervalSec": 30, + "severity": "p2", + "observer_applicable": "false" + }, { "name": "l1_gas_price_provider_insufficient_history", "title": "L1 gas price provider insufficient history", @@ -1549,7 +1577,7 @@ { "evaluator": { "params": [ - 2000.0 + 10000.0 ], "type": "gt" }, @@ -1650,7 +1678,7 @@ "for": "30s", "intervalSec": 30, "severity": "p3", - "observer_applicable": "true" + "observer_applicable": "false" }, { "name": "state_sync_stuck", diff --git a/crates/apollo_dashboard/src/alert_definitions.rs b/crates/apollo_dashboard/src/alert_definitions.rs index ec41e033a09..c73156678af 100644 --- a/crates/apollo_dashboard/src/alert_definitions.rs +++ b/crates/apollo_dashboard/src/alert_definitions.rs @@ -23,6 +23,7 @@ use apollo_l1_provider::metrics::{ L1_MESSAGE_SCRAPER_REORG_DETECTED, }; use apollo_mempool_p2p::metrics::MEMPOOL_P2P_NUM_CONNECTED_PEERS; +use apollo_metrics::MetricCommon; use apollo_storage::metrics::{ BATCHER_STORAGE_OPEN_READ_TRANSACTIONS, CLASS_MANAGER_STORAGE_OPEN_READ_TRANSACTIONS, @@ -35,8 +36,8 @@ use crate::alert_scenarios::block_production_delay::{ get_cende_write_blob_failure_once_alert, get_consensus_block_number_progress_is_slow_vec, get_consensus_p2p_peer_down_vec, + get_consensus_round_above_zero, get_consensus_round_above_zero_multiple_times_vec, - get_consensus_round_above_zero_vec, }; use crate::alert_scenarios::block_production_halt::{ get_batched_transactions_stuck_vec, @@ -70,6 +71,7 @@ use crate::alert_scenarios::tps::{ get_mempool_add_tx_idle, }; use crate::alert_scenarios::transaction_delays::{ + get_high_empty_blocks_ratio_alert_vec, get_http_server_avg_add_tx_latency_alert_vec, get_http_server_p95_add_tx_latency_alert_vec, get_mempool_p2p_peer_down_vec, @@ -95,6 +97,9 @@ use crate::alerts::{ PENDING_DURATION_DEFAULT, }; +/// Alerts that depend on block time can use this value to define their rule. +pub(crate) const BLOCK_TIME_SEC: f64 = 9.0; + pub fn get_dev_alerts_json_path(alert_env_filtering: AlertEnvFiltering) -> String { format!("crates/apollo_dashboard/resources/dev_grafana_alerts_{alert_env_filtering}.json") } @@ -522,6 +527,7 @@ pub fn get_apollo_alerts(alert_env_filtering: AlertEnvFiltering) -> Alerts { get_consensus_l1_gas_price_provider_failure(), get_consensus_l1_gas_price_provider_failure_once(), get_consensus_p2p_disconnections(), + get_consensus_round_above_zero(), get_consensus_votes_num_sent_messages_alert(), get_eth_to_strk_error_count_alert(), get_gateway_add_tx_idle(), @@ -550,7 +556,6 @@ pub fn get_apollo_alerts(alert_env_filtering: AlertEnvFiltering) -> Alerts { alerts.append(&mut get_consensus_block_number_stuck_vec()); alerts.append(&mut get_consensus_p2p_not_enough_peers_for_quorum_vec()); alerts.append(&mut get_consensus_p2p_peer_down_vec()); - alerts.append(&mut get_consensus_round_above_zero_vec()); alerts.append(&mut get_consensus_round_above_zero_multiple_times_vec()); alerts.append(&mut get_consensus_round_high_vec()); alerts.append(&mut get_eth_to_strk_success_count_alert_vec()); @@ -560,6 +565,7 @@ pub fn get_apollo_alerts(alert_env_filtering: AlertEnvFiltering) -> Alerts { alerts.append(&mut get_http_server_internal_error_ratio_vec()); alerts.append(&mut get_gateway_low_successful_transaction_rate_vec()); alerts.append(&mut get_http_server_p95_add_tx_latency_alert_vec()); + alerts.append(&mut get_high_empty_blocks_ratio_alert_vec()); alerts.append(&mut get_l1_gas_price_provider_insufficient_history_alert_vec()); alerts.append(&mut get_l1_gas_price_scraper_success_count_alert_vec()); alerts.append(&mut get_l1_message_scraper_no_successes_alert_vec()); diff --git a/crates/apollo_dashboard/src/alert_scenarios/block_production_delay.rs b/crates/apollo_dashboard/src/alert_scenarios/block_production_delay.rs index 70ebe2a7291..61a4571be20 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/block_production_delay.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/block_production_delay.rs @@ -1,7 +1,9 @@ use apollo_consensus::metrics::{CONSENSUS_BLOCK_NUMBER, CONSENSUS_ROUND_ABOVE_ZERO}; use apollo_consensus_manager::metrics::CONSENSUS_NUM_CONNECTED_PEERS; use apollo_consensus_orchestrator::metrics::CENDE_WRITE_BLOB_FAILURE; +use apollo_metrics::MetricCommon; +use crate::alert_definitions::BLOCK_TIME_SEC; use crate::alerts::{ Alert, AlertComparisonOp, @@ -15,11 +17,8 @@ use crate::alerts::{ PENDING_DURATION_DEFAULT, }; -/// The was a round larger than zero in the last hour. -fn get_consensus_round_above_zero( - alert_env_filtering: AlertEnvFiltering, - alert_severity: AlertSeverity, -) -> Alert { +/// There was a consensus round number higher than zero. +pub(crate) fn get_consensus_round_above_zero() -> Alert { Alert::new( "consensus_round_above_zero", "Consensus round above zero", @@ -32,26 +31,12 @@ fn get_consensus_round_above_zero( }], PENDING_DURATION_DEFAULT, EVALUATION_INTERVAL_SEC_DEFAULT, - alert_severity, + AlertSeverity::Informational, ObserverApplicability::NotApplicable, - alert_env_filtering, + AlertEnvFiltering::All, ) } -pub(crate) fn get_consensus_round_above_zero_vec() -> Vec { - vec![ - get_consensus_round_above_zero( - AlertEnvFiltering::MainnetStyleAlerts, - AlertSeverity::WorkingHours, - ), - get_consensus_round_above_zero( - AlertEnvFiltering::TestnetStyleAlerts, - AlertSeverity::WorkingHours, - ), - ] -} - -/// There were 5 times in the last 30 minutes that the round was larger than zero. fn get_consensus_round_above_zero_multiple_times( alert_env_filtering: AlertEnvFiltering, alert_severity: AlertSeverity, @@ -60,10 +45,10 @@ fn get_consensus_round_above_zero_multiple_times( "consensus_round_above_zero_multiple_times", "Consensus round above zero multiple times", AlertGroup::Consensus, - format!("increase({}[30m])", CONSENSUS_ROUND_ABOVE_ZERO.get_name_with_filter()), + format!("increase({}[10m])", CONSENSUS_ROUND_ABOVE_ZERO.get_name_with_filter()), vec![AlertCondition { comparison_op: AlertComparisonOp::GreaterThan, - comparison_value: 5.0, + comparison_value: 180.0 / BLOCK_TIME_SEC, logical_op: AlertLogicalOp::And, }], PENDING_DURATION_DEFAULT, @@ -147,7 +132,7 @@ fn get_consensus_p2p_peer_down( pub(crate) fn get_consensus_p2p_peer_down_vec() -> Vec { vec![ - get_consensus_p2p_peer_down(AlertEnvFiltering::MainnetStyleAlerts, AlertSeverity::Sos), + get_consensus_p2p_peer_down(AlertEnvFiltering::MainnetStyleAlerts, AlertSeverity::Regular), get_consensus_p2p_peer_down( AlertEnvFiltering::TestnetStyleAlerts, AlertSeverity::WorkingHours, @@ -174,7 +159,6 @@ pub(crate) fn get_cende_write_blob_failure_once_alert() -> Alert { ) } -/// Block number progressed slowly (< 10) in the last 2 minutes. fn get_consensus_block_number_progress_is_slow( alert_env_filtering: AlertEnvFiltering, alert_severity: AlertSeverity, @@ -189,7 +173,7 @@ fn get_consensus_block_number_progress_is_slow( ), vec![AlertCondition { comparison_op: AlertComparisonOp::LessThan, - comparison_value: 10.0, + comparison_value: 60.0 / BLOCK_TIME_SEC, logical_op: AlertLogicalOp::And, }], PENDING_DURATION_DEFAULT, @@ -204,7 +188,7 @@ pub(crate) fn get_consensus_block_number_progress_is_slow_vec() -> Vec { vec![ get_consensus_block_number_progress_is_slow( AlertEnvFiltering::MainnetStyleAlerts, - AlertSeverity::Sos, + AlertSeverity::Regular, ), get_consensus_block_number_progress_is_slow( AlertEnvFiltering::TestnetStyleAlerts, diff --git a/crates/apollo_dashboard/src/alert_scenarios/block_production_halt.rs b/crates/apollo_dashboard/src/alert_scenarios/block_production_halt.rs index 71aa03f329c..581a4a564ef 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/block_production_halt.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/block_production_halt.rs @@ -3,7 +3,9 @@ use std::time::Duration; use apollo_batcher::metrics::BATCHED_TRANSACTIONS; use apollo_consensus::metrics::{CONSENSUS_BLOCK_NUMBER, CONSENSUS_ROUND}; use apollo_consensus_manager::metrics::CONSENSUS_NUM_CONNECTED_PEERS; +use apollo_metrics::MetricCommon; +use crate::alert_definitions::BLOCK_TIME_SEC; use crate::alerts::{ Alert, AlertComparisonOp, @@ -185,7 +187,7 @@ fn get_consensus_round_high( format!("max_over_time({}[2m])", CONSENSUS_ROUND.get_name_with_filter()), vec![AlertCondition { comparison_op: AlertComparisonOp::GreaterThan, - comparison_value: 20.0, + comparison_value: 120.0 / BLOCK_TIME_SEC, logical_op: AlertLogicalOp::And, }], PENDING_DURATION_DEFAULT, diff --git a/crates/apollo_dashboard/src/alert_scenarios/l1_gas_prices.rs b/crates/apollo_dashboard/src/alert_scenarios/l1_gas_prices.rs index b3e9badc9dd..c45ec3c9745 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/l1_gas_prices.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/l1_gas_prices.rs @@ -3,6 +3,7 @@ use apollo_l1_gas_price::metrics::{ L1_GAS_PRICE_PROVIDER_INSUFFICIENT_HISTORY, L1_GAS_PRICE_SCRAPER_SUCCESS_COUNT, }; +use apollo_metrics::MetricCommon; use crate::alerts::{ Alert, diff --git a/crates/apollo_dashboard/src/alert_scenarios/l1_handlers.rs b/crates/apollo_dashboard/src/alert_scenarios/l1_handlers.rs index 7916d27700a..726c106d7cf 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/l1_handlers.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/l1_handlers.rs @@ -1,4 +1,5 @@ use apollo_l1_provider::metrics::L1_MESSAGE_SCRAPER_SUCCESS_COUNT; +use apollo_metrics::MetricCommon; use crate::alerts::{ Alert, diff --git a/crates/apollo_dashboard/src/alert_scenarios/mempool_size.rs b/crates/apollo_dashboard/src/alert_scenarios/mempool_size.rs index 0c14240b554..455ee5e0999 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/mempool_size.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/mempool_size.rs @@ -4,6 +4,7 @@ use apollo_mempool::metrics::{ MEMPOOL_POOL_SIZE, MEMPOOL_TRANSACTIONS_DROPPED, }; +use apollo_metrics::MetricCommon; use crate::alerts::{ Alert, @@ -29,7 +30,7 @@ fn get_mempool_pool_size_increase( MEMPOOL_POOL_SIZE.get_name_with_filter().to_string(), vec![AlertCondition { comparison_op: AlertComparisonOp::GreaterThan, - comparison_value: 2000.0, + comparison_value: 10000.0, logical_op: AlertLogicalOp::And, }], PENDING_DURATION_DEFAULT, @@ -44,7 +45,7 @@ pub(crate) fn get_mempool_pool_size_increase_vec() -> Vec { vec![ get_mempool_pool_size_increase( AlertEnvFiltering::MainnetStyleAlerts, - AlertSeverity::Regular, + AlertSeverity::DayOnly, ), get_mempool_pool_size_increase( AlertEnvFiltering::TestnetStyleAlerts, diff --git a/crates/apollo_dashboard/src/alert_scenarios/preconfirmed.rs b/crates/apollo_dashboard/src/alert_scenarios/preconfirmed.rs index 83bc266cfed..e9b1960827a 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/preconfirmed.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/preconfirmed.rs @@ -36,11 +36,13 @@ fn get_preconfirmed_block_not_written( ) } +use apollo_metrics::MetricCommon; + pub(crate) fn get_preconfirmed_block_not_written_vec() -> Vec { vec![ get_preconfirmed_block_not_written( AlertEnvFiltering::MainnetStyleAlerts, - AlertSeverity::Regular, + AlertSeverity::Sos, ), get_preconfirmed_block_not_written( AlertEnvFiltering::TestnetStyleAlerts, diff --git a/crates/apollo_dashboard/src/alert_scenarios/sync_halt.rs b/crates/apollo_dashboard/src/alert_scenarios/sync_halt.rs index 1171d5e0fdd..3436d796e9f 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/sync_halt.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/sync_halt.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use apollo_metrics::MetricCommon; use apollo_state_sync_metrics::metrics::{ CENTRAL_SYNC_CENTRAL_BLOCK_MARKER, STATE_SYNC_CLASS_MANAGER_MARKER, @@ -40,7 +41,7 @@ fn get_state_sync_lag( PENDING_DURATION_DEFAULT, EVALUATION_INTERVAL_SEC_DEFAULT, alert_severity, - ObserverApplicability::Applicable, + ObserverApplicability::NotApplicable, alert_env_filtering, ) } diff --git a/crates/apollo_dashboard/src/alert_scenarios/tps.rs b/crates/apollo_dashboard/src/alert_scenarios/tps.rs index e6297af1907..c15a2c1d778 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/tps.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/tps.rs @@ -6,6 +6,7 @@ use apollo_gateway::metrics::{ }; use apollo_http_server::metrics::ADDED_TRANSACTIONS_SUCCESS; use apollo_mempool::metrics::MEMPOOL_TRANSACTIONS_RECEIVED; +use apollo_metrics::MetricCommon; use crate::alerts::{ Alert, @@ -27,6 +28,7 @@ fn build_idle_alert( alert_group: AlertGroup, metric_name_with_filter: &str, duration: Duration, + alert_severity: AlertSeverity, ) -> Alert { Alert::new( alert_name, @@ -40,7 +42,7 @@ fn build_idle_alert( }], PENDING_DURATION_DEFAULT, EVALUATION_INTERVAL_SEC_DEFAULT, - AlertSeverity::Sos, + alert_severity, ObserverApplicability::NotApplicable, AlertEnvFiltering::All, ) @@ -53,26 +55,29 @@ pub(crate) fn get_http_server_no_successful_transactions() -> Alert { AlertGroup::HttpServer, &ADDED_TRANSACTIONS_SUCCESS.get_name_with_filter(), Duration::from_secs(30 * SECS_IN_MIN), + AlertSeverity::Regular, ) } pub(crate) fn get_gateway_add_tx_idle() -> Alert { build_idle_alert( - "gateway_add_tx_idle_all_sources", - "Gateway add_tx idle (all sources)", + "gateway_add_tx_idle_p2p_rpc", + "Gateway add_tx idle (p2p+rpc)", AlertGroup::Gateway, &GATEWAY_TRANSACTIONS_RECEIVED.get_name_with_filter(), Duration::from_secs(2 * SECS_IN_MIN), + AlertSeverity::Regular, ) } pub(crate) fn get_mempool_add_tx_idle() -> Alert { build_idle_alert( - "mempool_add_tx_idle_all_sources", - "Mempool add_tx idle (all sources)", + "mempool_add_tx_idle_p2p_rpc", + "Mempool add_tx idle (p2p+rpc)", AlertGroup::Mempool, &MEMPOOL_TRANSACTIONS_RECEIVED.get_name_with_filter(), Duration::from_secs(2 * SECS_IN_MIN), + AlertSeverity::Sos, ) } diff --git a/crates/apollo_dashboard/src/alert_scenarios/transaction_delays.rs b/crates/apollo_dashboard/src/alert_scenarios/transaction_delays.rs index 7c47d5daeae..a457337df5f 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/transaction_delays.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/transaction_delays.rs @@ -1,5 +1,7 @@ +use apollo_batcher::metrics::NUM_TRANSACTION_IN_BLOCK; use apollo_http_server::metrics::HTTP_SERVER_ADD_TX_LATENCY; use apollo_mempool_p2p::metrics::MEMPOOL_P2P_NUM_CONNECTED_PEERS; +use apollo_metrics::MetricCommon; use crate::alerts::{ Alert, @@ -122,7 +124,7 @@ pub(crate) fn get_http_server_p95_add_tx_latency_alert_vec() -> Vec { vec![ get_http_server_p95_add_tx_latency_alert( AlertEnvFiltering::MainnetStyleAlerts, - AlertSeverity::Regular, + AlertSeverity::WorkingHours, ), get_http_server_p95_add_tx_latency_alert( AlertEnvFiltering::TestnetStyleAlerts, @@ -130,3 +132,57 @@ pub(crate) fn get_http_server_p95_add_tx_latency_alert_vec() -> Vec { ), ] } + +fn get_high_empty_blocks_ratio_alert( + alert_env_filtering: AlertEnvFiltering, + alert_severity: AlertSeverity, + ratio: f64, + time_window_seconds: u64, +) -> Alert { + // Our histogram buckets are static and the smallest bucket is 0.001. + let zero_bucket = format!( + "{}, le=\"0.001\"}}", + NUM_TRANSACTION_IN_BLOCK + .get_name_with_filter() + .strip_suffix("}") + .expect("Metric name with filter should end with }") + ); + let total_count = NUM_TRANSACTION_IN_BLOCK.get_name_count_with_filter(); + + Alert::new( + "high_empty_blocks_ratio", + "High ratio of empty blocks", + AlertGroup::Batcher, + format!( + "sum(increase({zero_bucket}[{}s])) / clamp_min(sum(increase({total_count}[{}s])), 1)", + time_window_seconds, time_window_seconds + ), + vec![AlertCondition { + comparison_op: AlertComparisonOp::GreaterThan, + comparison_value: ratio, + logical_op: AlertLogicalOp::And, + }], + PENDING_DURATION_DEFAULT, + EVALUATION_INTERVAL_SEC_DEFAULT, + alert_severity, + ObserverApplicability::NotApplicable, + alert_env_filtering, + ) +} + +pub(crate) fn get_high_empty_blocks_ratio_alert_vec() -> Vec { + vec![ + get_high_empty_blocks_ratio_alert( + AlertEnvFiltering::MainnetStyleAlerts, + AlertSeverity::Sos, + 0.3, + 120, + ), + get_high_empty_blocks_ratio_alert( + AlertEnvFiltering::TestnetStyleAlerts, + AlertSeverity::Regular, + 0.6, + 300, + ), + ] +} diff --git a/crates/apollo_dashboard/src/alert_scenarios/transaction_failures.rs b/crates/apollo_dashboard/src/alert_scenarios/transaction_failures.rs index 7c90dbca404..04197c62f19 100644 --- a/crates/apollo_dashboard/src/alert_scenarios/transaction_failures.rs +++ b/crates/apollo_dashboard/src/alert_scenarios/transaction_failures.rs @@ -5,6 +5,7 @@ use apollo_http_server::metrics::{ ADDED_TRANSACTIONS_TOTAL, }; use apollo_mempool::metrics::{MEMPOOL_TRANSACTIONS_DROPPED, MEMPOOL_TRANSACTIONS_RECEIVED}; +use apollo_metrics::MetricCommon; use crate::alerts::{ Alert, @@ -57,7 +58,7 @@ pub(crate) fn get_http_server_high_transaction_failure_ratio() -> Alert { ), vec![AlertCondition { comparison_op: AlertComparisonOp::GreaterThan, - comparison_value: 0.3, + comparison_value: 0.5, logical_op: AlertLogicalOp::And, }], PENDING_DURATION_DEFAULT, diff --git a/crates/apollo_dashboard/src/bin/sequencer_dashboard_generator.rs b/crates/apollo_dashboard/src/bin/sequencer_dashboard_generator.rs index 1169baa2160..f066fef1936 100644 --- a/crates/apollo_dashboard/src/bin/sequencer_dashboard_generator.rs +++ b/crates/apollo_dashboard/src/bin/sequencer_dashboard_generator.rs @@ -6,13 +6,13 @@ use strum::IntoEnumIterator; /// Creates the dashboard and alerts json files. fn main() { - serialize_to_file(get_apollo_dashboard(), DEV_JSON_PATH); + serialize_to_file(&get_apollo_dashboard(), DEV_JSON_PATH); for alert_env_filtering in AlertEnvFiltering::iter() { if alert_env_filtering == AlertEnvFiltering::All { continue; // Skip the 'All' variant, as it used to cover all other options. } serialize_to_file( - get_apollo_alerts(alert_env_filtering), + &get_apollo_alerts(alert_env_filtering), &get_dev_alerts_json_path(alert_env_filtering), ); } diff --git a/crates/apollo_dashboard/src/dashboard.rs b/crates/apollo_dashboard/src/dashboard.rs index caeda1175db..5f2fa641d9a 100644 --- a/crates/apollo_dashboard/src/dashboard.rs +++ b/crates/apollo_dashboard/src/dashboard.rs @@ -8,6 +8,7 @@ use apollo_infra::metrics::{ use apollo_infra::requests::LABEL_NAME_REQUEST_VARIANT; use apollo_metrics::metrics::{ LabeledMetricHistogram, + MetricCommon, MetricCounter, MetricGauge, MetricHistogram, @@ -17,6 +18,8 @@ use serde::ser::{SerializeMap, SerializeStruct}; use serde::{Serialize, Serializer}; use serde_with::skip_serializing_none; +use crate::query_builder; + #[cfg(test)] #[path = "dashboard_test.rs"] mod dashboard_test; @@ -137,11 +140,40 @@ pub(crate) struct Panel { extra: ExtraParams, } +// helper: unify "String" and "Vec" +pub(crate) trait IntoExprs { + fn into_exprs(self) -> Vec; +} + +impl IntoExprs for String { + fn into_exprs(self) -> Vec { + vec![self] + } +} + +impl IntoExprs for &str { + fn into_exprs(self) -> Vec { + vec![self.to_string()] + } +} + +impl IntoExprs for Vec { + fn into_exprs(self) -> Vec { + self + } +} + +impl IntoExprs for Vec<&str> { + fn into_exprs(self) -> Vec { + self.into_iter().map(|s| s.to_string()).collect::>() + } +} + impl Panel { pub(crate) fn new( name: impl ToString, description: impl ToString, - exprs: Vec, + exprs: impl IntoExprs, panel_type: PanelType, ) -> Self { // A panel assigns a unique id to each of its expressions. Conventionally, we use letters @@ -149,6 +181,9 @@ impl Panel { const NUM_LETTERS: u8 = b'Z' - b'A' + 1; let name = name.to_string(); let description = description.to_string(); + + let exprs = exprs.into_exprs(); + assert!( exprs.len() <= NUM_LETTERS.into(), "Too many expressions ({} > {NUM_LETTERS}) in panel '{name}'.", @@ -156,6 +191,7 @@ impl Panel { ); Self { name, description, exprs, panel_type, extra: ExtraParams::default() } } + pub fn with_unit(mut self, unit: Unit) -> Self { self.extra.unit = Some(unit); self @@ -238,9 +274,10 @@ impl Panel { self.with_thresholds(ThresholdMode::Percentage, steps) } + // TODO(Tsabary): consider deleting. // TODO(Tsabary): unify relevant parts with `from_hist` to avoid code duplication. - // TODO(alonl): remove the _ prefix once the function is used. - pub(crate) fn _from_request_type_labeled_hist( + #[allow(dead_code)] + pub(crate) fn from_request_type_labeled_hist( metric: &LabeledMetricHistogram, panel_type: PanelType, request_label: &str, @@ -264,7 +301,7 @@ impl Panel { (rate({metric_name_with_filter_and_reason}[{HISTOGRAM_TIME_RANGE}])))", ) }) - .collect(), + .collect::>(), panel_type, ) } @@ -276,25 +313,24 @@ impl Panel { denominator_parts: &[&MetricCounter], duration: &str, ) -> Self { - let numerator_expr = - format!("increase({}[{}])", numerator.get_name_with_filter(), duration); + let numerator_expr = query_builder::increase(numerator, duration); let denominator_expr = denominator_parts .iter() - .map(|m| format!("increase({}[{}])", m.get_name_with_filter(), duration)) + .map(|m| query_builder::increase(*m, duration)) .collect::>() .join(" + "); let expr = format!("({numerator_expr} / ({denominator_expr}))"); - Self::new(name, description, vec![expr], PanelType::TimeSeries).with_unit(Unit::PercentUnit) + Self::new(name, description, expr, PanelType::TimeSeries).with_unit(Unit::PercentUnit) } pub(crate) fn from_counter(metric: &MetricCounter, panel_type: PanelType) -> Self { Self::new( metric.get_name(), metric.get_description(), - vec![metric.get_name_with_filter().to_string()], + metric.get_name_with_filter().to_string(), panel_type, ) } @@ -303,26 +339,60 @@ impl Panel { Self::new( metric.get_name(), metric.get_description(), - vec![metric.get_name_with_filter().to_string()], + metric.get_name_with_filter().to_string(), panel_type, ) } - pub(crate) fn from_hist(metric: &MetricHistogram, panel_type: PanelType) -> Self { + fn from_hist_helper( + metric_name_with_filter: impl AsRef, + name: impl ToString, + description: impl ToString, + sum_by: impl AsRef, + ) -> Self { Self::new( - metric.get_name(), - metric.get_description(), + name.to_string(), + description.to_string(), HISTOGRAM_QUANTILES .iter() .map(|q| { format!( - "histogram_quantile({q:.2}, sum by (le) \ + "histogram_quantile({q:.2}, sum by ({}) \ (rate({}[{HISTOGRAM_TIME_RANGE}])))", - metric.get_name_with_filter(), + sum_by.as_ref(), + metric_name_with_filter.as_ref(), ) }) + .collect::>(), + PanelType::TimeSeries, + ) + } + + pub(crate) fn from_hist( + metric: &MetricHistogram, + name: impl ToString, + description: impl ToString, + ) -> Self { + Self::from_hist_helper(metric.get_name_with_filter(), name, description, "le") + } + + pub(crate) fn from_labeled_hist( + metric: &LabeledMetricHistogram, + name: impl ToString, + description: impl ToString, + ) -> Self { + let group_label = metric.get_label_name(); + Self::from_hist_helper( + metric.get_name_with_filter(), + name, + description, + format!("le, {}", group_label), + ) + .with_legends( + HISTOGRAM_QUANTILES + .iter() + .map(|q| format!("{:.2} {{{{{}}}}}", q, group_label)) .collect(), - panel_type, ) } } @@ -334,7 +404,7 @@ pub fn traffic_light_thresholds(yellow: f64, red: f64) -> Vec<(&'static str, Opt // There is no equivalent for LabeledPanels because they are less straightforward than // UnlabeledPanels and require an aggregation of metrics more often, for example the panels created -// using `get_multi_metric_panel`. +// using [`get_multi_metric_panel`]. /// A struct that contains all unlabeled panels for a given metrics struct. struct UnlabeledPanels(Vec); @@ -346,7 +416,11 @@ impl From<&LocalClientMetrics> for UnlabeledPanels { impl From<&RemoteClientMetrics> for UnlabeledPanels { fn from(metrics: &RemoteClientMetrics) -> Self { - Self(vec![Panel::from_hist(metrics.get_attempts_metric(), PanelType::TimeSeries)]) + Self(vec![Panel::from_hist( + metrics.get_attempts_metric(), + metrics.get_attempts_metric().get_name(), + metrics.get_attempts_metric().get_description(), + )]) } } @@ -390,7 +464,9 @@ impl From<&RemoteServerMetrics> for UnlabeledPanels { } } -pub(crate) fn _create_request_type_labeled_hist_panels( +// TODO(Tsabary): consider deleting. +#[allow(dead_code)] +pub(crate) fn create_request_type_labeled_hist_panels( metric: &LabeledMetricHistogram, panel_type: PanelType, ) -> Vec { @@ -398,14 +474,14 @@ pub(crate) fn _create_request_type_labeled_hist_panels( .get_flat_label_values() .into_iter() .map(|request_label| { - Panel::_from_request_type_labeled_hist(metric, panel_type, request_label) + Panel::from_request_type_labeled_hist(metric, panel_type, request_label) }) .collect() } // For a given request label and vector of labeled histogram metrics, create a panel with multiple // expressions. -pub(crate) fn get_multi_metric_panel( +fn get_multi_metric_panel( panel_name: &str, panel_description: &str, request_label: &str, @@ -482,7 +558,7 @@ impl Serialize for Row { } // This function assumes that all metrics share the same labels. -fn get_request_type_labeled_panels( +fn get_request_type_panels( labeled_metrics: &Vec<&LabeledMetricHistogram>, panel_class_name: &str, ) -> Vec { @@ -508,32 +584,32 @@ fn get_request_type_labeled_panels( panels } -pub(crate) fn get_labeled_client_panels( +fn get_infra_client_panels( local_client_metrics: &LocalClientMetrics, remote_client_metrics: &RemoteClientMetrics, ) -> Vec { let mut labeled_metrics: Vec<&LabeledMetricHistogram> = local_client_metrics.get_all_labeled_metrics(); labeled_metrics.extend(remote_client_metrics.get_all_labeled_metrics()); - get_request_type_labeled_panels(&labeled_metrics, "client") + get_request_type_panels(&labeled_metrics, "client") } -pub(crate) fn get_labeled_server_panels( +fn get_infra_server_panels( local_server_metrics: &LocalServerMetrics, remote_server_metrics: &RemoteServerMetrics, ) -> Vec { let mut labeled_metrics: Vec<&LabeledMetricHistogram> = local_server_metrics.get_all_labeled_metrics(); labeled_metrics.extend(remote_server_metrics.get_all_labeled_metrics()); - get_request_type_labeled_panels(&labeled_metrics, "server") + get_request_type_panels(&labeled_metrics, "server") } pub(crate) fn get_component_infra_row(row_name: &'static str, metrics: &InfraMetrics) -> Row { - let labeled_client_panels = get_labeled_client_panels( + let labeled_client_panels = get_infra_client_panels( metrics.get_local_client_metrics(), metrics.get_remote_client_metrics(), ); - let labeled_server_panels = get_labeled_server_panels( + let labeled_server_panels = get_infra_server_panels( metrics.get_local_server_metrics(), metrics.get_remote_server_metrics(), ); @@ -548,3 +624,14 @@ pub(crate) fn get_component_infra_row(row_name: &'static str, metrics: &InfraMet Row::new(row_name, panels) } + +/// Returns a PromQL expression that calculates the time since the last increase of the given +/// metric. Assumes there was an increase in the last 12 hours. +pub(crate) fn get_time_since_last_increase_expr(metric_name: &str) -> String { + const TIME_RANGE: &str = "12h"; + format!( + // The max over time is the timestamp of the last increase in the last 12 hours. + "time() - max_over_time((timestamp(increase({metric_name}[{TIME_RANGE}])) != \ + 0))[{TIME_RANGE}:])" + ) +} diff --git a/crates/apollo_dashboard/src/dashboard_definitions.rs b/crates/apollo_dashboard/src/dashboard_definitions.rs index 1a64c8dc518..192942e54c4 100644 --- a/crates/apollo_dashboard/src/dashboard_definitions.rs +++ b/crates/apollo_dashboard/src/dashboard_definitions.rs @@ -16,10 +16,11 @@ use crate::panels::consensus::{ get_cende_row, get_consensus_p2p_row, get_consensus_row, - get_panel_cende_write_blob_failure, + get_panel_consensus_block_number_diff_from_sync, + get_panel_consensus_block_time_avg, get_panel_consensus_round, }; -use crate::panels::gateway::{get_gateway_row, get_panel_gateway_transactions_failure_rate}; +use crate::panels::gateway::{get_gateway_row, get_panel_gateway_add_tx_failure_by_reason}; use crate::panels::http_server::{ get_http_server_row, get_panel_http_server_transactions_received_rate, @@ -29,12 +30,9 @@ use crate::panels::l1_provider::get_l1_provider_row; use crate::panels::mempool::get_mempool_row; use crate::panels::mempool_p2p::get_mempool_p2p_row; use crate::panels::pod_metrics::get_pod_metrics_row; +use crate::panels::reverts::get_reverts_row; use crate::panels::sierra_compiler::get_compile_to_casm_row; -use crate::panels::state_sync::{ - get_panel_state_sync_diff_from_central, - get_state_sync_p2p_row, - get_state_sync_row, -}; +use crate::panels::state_sync::get_state_sync_row; use crate::panels::storage::get_storage_row; use crate::panels::tokio::get_tokio_row; @@ -48,12 +46,12 @@ fn get_overview_row() -> Row { Row::new( "Overview", vec![ + get_panel_consensus_block_time_avg(), get_panel_consensus_round(), - get_panel_cende_write_blob_failure(), get_panel_http_server_transactions_received_rate(), get_panel_batched_transactions_rate(), - get_panel_state_sync_diff_from_central(), - get_panel_gateway_transactions_failure_rate(), + get_panel_consensus_block_number_diff_from_sync(), + get_panel_gateway_add_tx_failure_by_reason(), ], ) .expand() @@ -76,9 +74,9 @@ pub fn get_apollo_dashboard() -> Dashboard { get_blockifier_row(), get_compile_to_casm_row(), get_consensus_p2p_row(), - get_state_sync_p2p_row(), - get_storage_row(), get_mempool_p2p_row(), + get_reverts_row(), + get_storage_row(), get_tokio_row(), get_component_infra_row("Batcher Infra", &BATCHER_INFRA_METRICS), get_component_infra_row("State Sync Infra", &STATE_SYNC_INFRA_METRICS), diff --git a/crates/apollo_dashboard/src/dashboard_definitions_test.rs b/crates/apollo_dashboard/src/dashboard_definitions_test.rs index 0101bfcefd8..8fec5ed45cd 100644 --- a/crates/apollo_dashboard/src/dashboard_definitions_test.rs +++ b/crates/apollo_dashboard/src/dashboard_definitions_test.rs @@ -11,13 +11,13 @@ const FIX_BINARY_NAME: &str = "sequencer_dashboard_generator"; // file, run: cargo run --bin sequencer_dashboard_generator -q #[test] fn default_dev_grafana_dashboard() { - serialize_to_file_test(get_apollo_dashboard(), DEV_JSON_PATH, FIX_BINARY_NAME); + serialize_to_file_test(&get_apollo_dashboard(), DEV_JSON_PATH, FIX_BINARY_NAME); for alert_env_filtering in AlertEnvFiltering::iter() { if alert_env_filtering == AlertEnvFiltering::All { continue; // Skip the 'All' variant, as it used to cover all other options. } serialize_to_file_test( - get_apollo_alerts(alert_env_filtering), + &get_apollo_alerts(alert_env_filtering), &get_dev_alerts_json_path(alert_env_filtering), FIX_BINARY_NAME, ); diff --git a/crates/apollo_dashboard/src/dashboard_test.rs b/crates/apollo_dashboard/src/dashboard_test.rs index e2b3ddbc4ba..adfb32307fd 100644 --- a/crates/apollo_dashboard/src/dashboard_test.rs +++ b/crates/apollo_dashboard/src/dashboard_test.rs @@ -1,5 +1,5 @@ use apollo_infra_utils::test_utils::assert_json_eq; -use apollo_metrics::metrics::{MetricCounter, MetricScope}; +use apollo_metrics::metrics::{MetricCommon, MetricCounter, MetricScope}; use crate::alerts::{ Alert, @@ -91,7 +91,7 @@ fn test_ratio_time_series() { #[test] fn test_extra_params() { - let panel_with_extra_params = Panel::new("x", "x", vec!["y".to_string()], PanelType::Stat) + let panel_with_extra_params = Panel::new("x", "x", "y".to_string(), PanelType::Stat) .with_unit(Unit::Bytes) .show_percent_change() .with_log_query("Query") @@ -118,7 +118,7 @@ fn test_extra_params() { ); assert_eq!(panel_with_extra_params.extra.legends, Some(vec!["a".to_string()])); - let panel_without_extra_params = Panel::new("x", "x", vec!["y".to_string()], PanelType::Stat); + let panel_without_extra_params = Panel::new("x", "x", "y".to_string(), PanelType::Stat); assert!(panel_without_extra_params.extra.unit.is_none()); assert!(panel_without_extra_params.extra.show_percent_change.is_none()); assert!(panel_without_extra_params.extra.log_query.is_none()); @@ -129,7 +129,7 @@ fn test_extra_params() { #[test] #[should_panic] fn thresholds_first_step_must_be_none() { - let _ = Panel::new("x", "x", vec!["y".into()], PanelType::Stat).with_absolute_thresholds(vec![ + let _ = Panel::new("x", "x", "y", PanelType::Stat).with_absolute_thresholds(vec![ ("green", Some(0.0)), // illegal: first step must be None ("red", Some(80.0)), ]); @@ -138,7 +138,7 @@ fn thresholds_first_step_must_be_none() { #[test] #[should_panic] fn thresholds_must_be_strictly_increasing() { - let _ = Panel::new("x", "x", vec!["y".into()], PanelType::Stat).with_absolute_thresholds(vec![ + let _ = Panel::new("x", "x", "y", PanelType::Stat).with_absolute_thresholds(vec![ ("green", None), ("red", Some(90.0)), ("yellow", Some(80.0)), // illegal: not increasing @@ -148,5 +148,5 @@ fn thresholds_must_be_strictly_increasing() { #[test] #[should_panic] fn amount_of_legends_must_match_amount_of_exprs() { - let _ = Panel::new("x", "x", vec!["y".into()], PanelType::Stat).with_legends(vec!["a", "b"]); + let _ = Panel::new("x", "x", "y", PanelType::Stat).with_legends(vec!["a", "b"]); } diff --git a/crates/apollo_dashboard/src/lib.rs b/crates/apollo_dashboard/src/lib.rs index 45ae7a90646..ba53c0fb16a 100644 --- a/crates/apollo_dashboard/src/lib.rs +++ b/crates/apollo_dashboard/src/lib.rs @@ -6,3 +6,5 @@ pub mod dashboard_definitions; #[cfg(test)] mod metric_definitions_test; mod panels; + +mod query_builder; diff --git a/crates/apollo_dashboard/src/panels.rs b/crates/apollo_dashboard/src/panels.rs index e4c1287da7c..e39b84f2b7e 100644 --- a/crates/apollo_dashboard/src/panels.rs +++ b/crates/apollo_dashboard/src/panels.rs @@ -9,6 +9,7 @@ pub(crate) mod l1_provider; pub(crate) mod mempool; pub(crate) mod mempool_p2p; pub(crate) mod pod_metrics; +pub(crate) mod reverts; pub(crate) mod sierra_compiler; pub(crate) mod state_sync; pub(crate) mod storage; diff --git a/crates/apollo_dashboard/src/panels/batcher.rs b/crates/apollo_dashboard/src/panels/batcher.rs index 81231ef031d..12b66b07f1b 100644 --- a/crates/apollo_dashboard/src/panels/batcher.rs +++ b/crates/apollo_dashboard/src/panels/batcher.rs @@ -12,14 +12,19 @@ use apollo_consensus_orchestrator::metrics::{ CONSENSUS_NUM_BATCHES_IN_PROPOSAL, CONSENSUS_NUM_TXS_IN_PROPOSAL, }; +use apollo_metrics::MetricCommon; use crate::dashboard::{Panel, PanelType, Row, Unit}; +use crate::query_builder::{increase, sum_by_label, DisplayMethod, DEFAULT_DURATION}; fn get_panel_validator_wasted_txs() -> Panel { Panel::new( "Proposal Validation: Wasted TXs", - "Number of txs executed by the validator but excluded from the block (10m window)", - vec![format!("increase({}[10m])", VALIDATOR_WASTED_TXS.get_name_with_filter())], + format!( + "Number of txs executed by the validator but excluded from the block \ + ({DEFAULT_DURATION} window)", + ), + increase(&VALIDATOR_WASTED_TXS, DEFAULT_DURATION), PanelType::TimeSeries, ) .with_log_query("Finished building block as validator. Started executing") @@ -28,8 +33,11 @@ fn get_panel_validator_wasted_txs() -> Panel { fn get_panel_proposer_deferred_txs() -> Panel { Panel::new( "Proposal Build: Deferred TXs", - "Number of txs started execution by the proposer but excluded from the block (10m window)", - vec![format!("increase({}[10m])", PROPOSER_DEFERRED_TXS.get_name_with_filter())], + format!( + "Number of txs started execution by the proposer but excluded from the block \ + ({DEFAULT_DURATION} window)", + ), + increase(&PROPOSER_DEFERRED_TXS, DEFAULT_DURATION), PanelType::TimeSeries, ) .with_log_query("Finished building block as proposer. Started executing") @@ -39,30 +47,31 @@ fn get_panel_storage_height() -> Panel { Panel::new( "Storage Height", "The height of the batcher's storage", - vec![STORAGE_HEIGHT.get_name_with_filter().to_string()], + STORAGE_HEIGHT.get_name_with_filter().to_string(), PanelType::Stat, ) + .with_log_query("Committing block at height") } fn get_panel_rejection_reverted_ratio() -> Panel { + let rejected_txs_expr = increase(&REJECTED_TRANSACTIONS, DEFAULT_DURATION); + let reverted_txs_expr = increase(&REVERTED_TRANSACTIONS, DEFAULT_DURATION); + let denominator_expr = format!( - "(increase({}[10m]) + increase({}[10m]) + increase({}[10m]))", - REJECTED_TRANSACTIONS.get_name_with_filter(), - REVERTED_TRANSACTIONS.get_name_with_filter(), - BATCHED_TRANSACTIONS.get_name_with_filter(), + "({} + {} + {})", + rejected_txs_expr, + reverted_txs_expr, + increase(&BATCHED_TRANSACTIONS, DEFAULT_DURATION), ); Panel::new( "Rejected / Reverted TXs Ratio", - "Ratio of rejected / reverted transactions out of all processed txs (10m window)", + format!( + "Ratio of rejected / reverted transactions out of all processed txs \ + ({DEFAULT_DURATION} window)" + ), vec![ - format!( - "increase({}[10m]) / {denominator_expr}", - REJECTED_TRANSACTIONS.get_name_with_filter(), - ), - format!( - "increase({}[10m]) / {denominator_expr}", - REVERTED_TRANSACTIONS.get_name_with_filter(), - ), + format!("{rejected_txs_expr} / {denominator_expr}"), + format!("{reverted_txs_expr} / {denominator_expr}"), ], PanelType::TimeSeries, ) @@ -74,21 +83,23 @@ pub(crate) fn get_panel_batched_transactions_rate() -> Panel { Panel::new( "Batched Transactions Rate (TPS)", "The rate of transactions batched by the Batcher (1m window)", - vec![format!("rate({}[1m])", BATCHED_TRANSACTIONS.get_name_with_filter())], + format!("rate({}[1m])", BATCHED_TRANSACTIONS.get_name_with_filter()), PanelType::TimeSeries, ) + .with_log_query("BATCHER_FIN_VALIDATOR") } fn get_panel_block_close_reasons() -> Panel { Panel::new( "Block Close Reasons", - "Number of blocks closed by reason (10m window)", - vec![format!( - "sum by ({}) (increase({}[10m]))", + format!("Number of blocks closed by reason ({} window)", DEFAULT_DURATION), + sum_by_label( + &BLOCK_CLOSE_REASON, LABEL_NAME_BLOCK_CLOSE_REASON, - BLOCK_CLOSE_REASON.get_name_with_filter() - )], - PanelType::TimeSeries, + DisplayMethod::Increase(DEFAULT_DURATION), + false, + ), + PanelType::Stat, ) .with_log_query("\"Block builder deadline reached.\" OR \"Block is full.\"") } @@ -97,7 +108,7 @@ fn get_panel_num_batches_in_proposal() -> Panel { Panel::new( "Number of Chunks in Proposal", "The number of transaction batches received in a valid proposal", - vec![CONSENSUS_NUM_BATCHES_IN_PROPOSAL.get_name_with_filter().to_string()], + CONSENSUS_NUM_BATCHES_IN_PROPOSAL.get_name_with_filter().to_string(), PanelType::TimeSeries, ) } @@ -106,9 +117,10 @@ fn get_panel_num_txs_in_proposal() -> Panel { Panel::new( "Number of Transactions in Proposal", "The total number of individual transactions in a valid proposal received", - vec![CONSENSUS_NUM_TXS_IN_PROPOSAL.get_name_with_filter().to_string()], + CONSENSUS_NUM_TXS_IN_PROPOSAL.get_name_with_filter().to_string(), PanelType::TimeSeries, ) + .with_log_query("BATCHER_FIN_PROPOSER") } pub(crate) fn get_batcher_row() -> Row { diff --git a/crates/apollo_dashboard/src/panels/blockifier.rs b/crates/apollo_dashboard/src/panels/blockifier.rs index 6f6fdc8ab65..37e0174c8cb 100644 --- a/crates/apollo_dashboard/src/panels/blockifier.rs +++ b/crates/apollo_dashboard/src/panels/blockifier.rs @@ -1,3 +1,9 @@ +use apollo_batcher::metrics::{ + NUM_TRANSACTION_IN_BLOCK, + PROVING_GAS_IN_LAST_BLOCK, + SIERRA_GAS_IN_LAST_BLOCK, +}; +use apollo_metrics::MetricCommon; use blockifier::metrics::{ BLOCKIFIER_METRIC_RATE_DURATION, CALLS_RUNNING_NATIVE, @@ -10,6 +16,8 @@ use blockifier::metrics::{ use crate::dashboard::{Panel, PanelType, Row}; +const DENOMINATOR_DIVISOR_FOR_READABILITY: f64 = 1_000_000_000.0; + // TODO(MatanL/Shahak): use clamp_min(X, 1) on denom to avoid division by zero. fn get_panel_blockifier_state_reader_class_cache_miss_ratio() -> Panel { Panel::ratio_time_series( @@ -47,6 +55,40 @@ fn get_panel_native_execution_ratio() -> Panel { ) } +fn get_panel_transactions_per_block() -> Panel { + Panel::from_hist( + &NUM_TRANSACTION_IN_BLOCK, + "Transactions Per Block", + "The number of transactions per block", + ) +} + +fn get_panel_sierra_gas_in_last_block() -> Panel { + Panel::new( + "Average Sierra Gas Usage in Block", + "The average sierra gas usage in block (10m window)", + format!( + "avg_over_time({}[10m])/{}", + SIERRA_GAS_IN_LAST_BLOCK.get_name_with_filter(), + DENOMINATOR_DIVISOR_FOR_READABILITY + ), + PanelType::TimeSeries, + ) +} + +fn get_panel_proving_gas_in_last_block() -> Panel { + Panel::new( + "Average Proving Gas Usage in Block", + "The average proving gas usage in block (10m window)", + format!( + "avg_over_time({}[10m])/{}", + PROVING_GAS_IN_LAST_BLOCK.get_name_with_filter(), + DENOMINATOR_DIVISOR_FOR_READABILITY + ), + PanelType::TimeSeries, + ) +} + pub(crate) fn get_blockifier_row() -> Row { Row::new( "Blockifier", @@ -55,6 +97,9 @@ pub(crate) fn get_blockifier_row() -> Row { get_panel_blockifier_state_reader_native_class_returned_ratio(), get_panel_native_compilation_error(), get_panel_native_execution_ratio(), + get_panel_transactions_per_block(), + get_panel_sierra_gas_in_last_block(), + get_panel_proving_gas_in_last_block(), ], ) } diff --git a/crates/apollo_dashboard/src/panels/consensus.rs b/crates/apollo_dashboard/src/panels/consensus.rs index ad0a989e82e..d8825b2be69 100644 --- a/crates/apollo_dashboard/src/panels/consensus.rs +++ b/crates/apollo_dashboard/src/panels/consensus.rs @@ -4,12 +4,15 @@ use apollo_consensus::metrics::{ CONSENSUS_BUILD_PROPOSAL_FAILED, CONSENSUS_BUILD_PROPOSAL_TOTAL, CONSENSUS_CONFLICTING_VOTES, + CONSENSUS_DECISIONS_REACHED_AS_PROPOSER, CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS, CONSENSUS_DECISIONS_REACHED_BY_SYNC, CONSENSUS_PROPOSALS_INVALID, CONSENSUS_PROPOSALS_RECEIVED, CONSENSUS_PROPOSALS_VALIDATED, CONSENSUS_ROUND, + CONSENSUS_ROUND_ABOVE_ZERO, + CONSENSUS_ROUND_ADVANCES, CONSENSUS_TIMEOUTS, LABEL_NAME_TIMEOUT_TYPE, }; @@ -35,252 +38,263 @@ use apollo_consensus_orchestrator::metrics::{ LABEL_CENDE_FAILURE_REASON, LABEL_VALIDATE_PROPOSAL_FAILURE_REASON, }; +use apollo_metrics::MetricCommon; use apollo_network::network_manager::metrics::{ LABEL_NAME_BROADCAST_DROP_REASON, LABEL_NAME_EVENT_TYPE, }; use apollo_state_sync_metrics::metrics::STATE_SYNC_CLASS_MANAGER_MARKER; -use crate::dashboard::{Panel, PanelType, Row, Unit, HISTOGRAM_QUANTILES, HISTOGRAM_TIME_RANGE}; +use crate::dashboard::{Panel, PanelType, Row, Unit}; +use crate::query_builder::{ + increase, + sum_by_label, + DisplayMethod, + DEFAULT_DURATION, + RANGE_DURATION, +}; + +// The key events that are relevant to the consensus panel. +const CONSENSUS_KEY_EVENTS_LOG_QUERY: &str = + "\"START_HEIGHT:\" OR \"START_ROUND\" OR textPayload=~\"DECISION_REACHED\" OR \ + \"PROPOSAL_FAILED\" OR \"Proposal succeeded\" OR \"Applying Timeout\" OR \"Accepting\" OR \ + \"Broadcasting\""; fn get_panel_consensus_block_number() -> Panel { Panel::new( "Consensus Height", "The block height the node is currently working on", - vec![CONSENSUS_BLOCK_NUMBER.get_name_with_filter().to_string()], + CONSENSUS_BLOCK_NUMBER.get_name_with_filter().to_string(), PanelType::Stat, ) + .with_log_query( + "\"START_HEIGHT: running consensus for height\" OR \"Start building proposal\" OR \"Start \ + validating proposal\"", + ) + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } -fn get_panel_consensus_block_number_diff_from_sync() -> Panel { + +pub(crate) fn get_panel_consensus_block_number_diff_from_sync() -> Panel { Panel::new( "Consensus Height Diff From Sync", "The difference between the consensus height and the sync height", - vec![format!( + format!( "({} - {})", CONSENSUS_BLOCK_NUMBER.get_name_with_filter(), STATE_SYNC_CLASS_MANAGER_MARKER.get_name_with_filter() - )], + ), PanelType::TimeSeries, ) } + pub(crate) fn get_panel_consensus_round() -> Panel { Panel::new( "Consensus Round", "The round the node is currently working on", - vec![CONSENSUS_ROUND.get_name_with_filter().to_string()], + CONSENSUS_ROUND.get_name_with_filter().to_string(), + PanelType::TimeSeries, + ) + .with_log_query("\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) +} + +pub(crate) fn get_panel_consensus_round_advanced() -> Panel { + Panel::new( + "Consensus Round Advanced", + format!( + "The number of times the consensus round advanced (counter is increased whenever \ + round > 0) ({DEFAULT_DURATION} window)", + ), + increase(&CONSENSUS_ROUND_ADVANCES, DEFAULT_DURATION), + PanelType::TimeSeries, + ) + .with_log_query("\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) +} + +fn get_panel_consensus_round_above_zero() -> Panel { + Panel::new( + "Consensus Round Above Zero", + "Occurances where the consensus round was 1, relative to displayed range", + format!( + "{m} - ({m} @ start())", + m = CONSENSUS_ROUND_ABOVE_ZERO.get_name_with_filter().to_string() + ), PanelType::TimeSeries, ) + .with_log_query("\"START_ROUND\" OR \"PROPOSAL_FAILED\" OR textPayload=~\"DECISION_REACHED\"") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } -fn get_panel_consensus_block_time_avg() -> Panel { + +pub(crate) fn get_panel_consensus_block_time_avg() -> Panel { Panel::new( "Average Block Time", - "Average block time (10m window)", - vec![format!("1 / rate({}[10m])", CONSENSUS_BLOCK_NUMBER.get_name_with_filter())], + "Average block time (1m window)", + format!("1 / rate({}[1m])", CONSENSUS_BLOCK_NUMBER.get_name_with_filter()), PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } + fn get_panel_consensus_decisions_reached_by_consensus() -> Panel { Panel::new( "Decisions Reached By Consensus", - "The number of decisions reached by way of consensus (10m window)", - vec![format!( - "increase({}[10m])", - CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS.get_name_with_filter() - )], + format!("The number of decisions reached by way of consensus ({DEFAULT_DURATION} window)",), + increase(&CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS, DEFAULT_DURATION), PanelType::TimeSeries, ) + .with_log_query("DECISION_REACHED: Decision reached for round") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_consensus_decisions_reached_by_sync() -> Panel { Panel::new( "Decisions Reached By Sync", - "The number of decisions reached by way of sync (10m window)", - vec![format!( - "increase({}[10m])", - CONSENSUS_DECISIONS_REACHED_BY_SYNC.get_name_with_filter() - )], + format!("The number of decisions reached by way of sync ({DEFAULT_DURATION} window)",), + increase(&CONSENSUS_DECISIONS_REACHED_BY_SYNC, DEFAULT_DURATION), PanelType::TimeSeries, ) + .with_log_query("Decision learned via sync protocol.") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_consensus_proposals_received() -> Panel { Panel::new( "Proposal Validation: Number of Received Proposals", - "The number of proposals received from the network (10m window)", - vec![format!("increase({}[10m])", CONSENSUS_PROPOSALS_RECEIVED.get_name_with_filter())], + format!("The number of proposals received from the network ({DEFAULT_DURATION} window)",), + increase(&CONSENSUS_PROPOSALS_RECEIVED, DEFAULT_DURATION), PanelType::TimeSeries, ) } + fn get_panel_consensus_proposals_validated() -> Panel { Panel::new( "Proposal Validation: Number of Validated Proposals", - "The number of proposals received and validated successfully (10m window)", - vec![format!("increase({}[10m])", CONSENSUS_PROPOSALS_VALIDATED.get_name_with_filter())], + format!( + "The number of proposals received and validated successfully ({DEFAULT_DURATION} \ + window)", + ), + increase(&CONSENSUS_PROPOSALS_VALIDATED, DEFAULT_DURATION), PanelType::TimeSeries, ) + .with_log_query("\"Validated proposal.\" OR \"PROPOSAL_FAILED\"") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_consensus_proposals_invalid() -> Panel { Panel::new( "Proposal Validation: Number of Invalid Proposals", - "The number of proposals received and failed validation (10m window)", - vec![format!("increase({}[10m])", CONSENSUS_PROPOSALS_INVALID.get_name_with_filter())], + format!( + "The number of proposals received and failed validation ({DEFAULT_DURATION} window)", + ), + increase(&CONSENSUS_PROPOSALS_INVALID, DEFAULT_DURATION), PanelType::TimeSeries, ) + .with_log_query("\"Validated proposal.\" OR \"PROPOSAL_FAILED\"") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_validate_proposal_failure() -> Panel { Panel::new( "Proposal Validation: Proposal Failure by Reason", "The number of validate proposal failures (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range])) > 0", + sum_by_label( + &CONSENSUS_VALIDATE_PROPOSAL_FAILURE, LABEL_VALIDATE_PROPOSAL_FAILURE_REASON, - CONSENSUS_VALIDATE_PROPOSAL_FAILURE.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + true, + ), PanelType::Stat, ) .with_log_query("PROPOSAL_FAILED: Proposal failed as validator") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_consensus_build_proposal_total() -> Panel { Panel::new( "Proposal Build: Number of Proposals Started", - "The number of proposals that started building (10m window)", - vec![format!("increase({}[10m])", CONSENSUS_BUILD_PROPOSAL_TOTAL.get_name_with_filter())], + format!("The number of proposals that started building ({DEFAULT_DURATION} window)",), + increase(&CONSENSUS_BUILD_PROPOSAL_TOTAL, DEFAULT_DURATION), PanelType::TimeSeries, ) } + fn get_panel_consensus_build_proposal_failed() -> Panel { Panel::new( "Proposal Build: Number of Proposals Failed", - "The number of proposals that failed to be built (10m window)", - vec![format!("increase({}[10m])", CONSENSUS_BUILD_PROPOSAL_FAILED.get_name_with_filter())], + format!("The number of proposals that failed to be built ({DEFAULT_DURATION} window)",), + increase(&CONSENSUS_BUILD_PROPOSAL_FAILED, DEFAULT_DURATION), PanelType::TimeSeries, ) } + fn get_panel_build_proposal_failure() -> Panel { Panel::new( "Proposal Build: Proposal Failure by Reason", "The number of build proposal failures (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range])) > 0", + sum_by_label( + &CONSENSUS_BUILD_PROPOSAL_FAILURE, LABEL_BUILD_PROPOSAL_FAILURE_REASON, - CONSENSUS_BUILD_PROPOSAL_FAILURE.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + true, + ), PanelType::Stat, ) .with_log_query("PROPOSAL_FAILED: Proposal failed as proposer") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_consensus_timeouts_by_type() -> Panel { Panel::new( "Consensus Timeouts By Type", - "The number of times consensus has timed out by type (10m window). \n- TimeoutPropose: as \ - proposer, didn’t finish building in time; as validator, either didn’t receive the \ - proposal or didn’t finish validation in time.\n- TimeoutPrevote: the node voted and \ - received a quorum of prevotes, but not on the same value.\n- TimeoutPrecommit: didn’t \ - finish validation but quorum of precommits received, or it finished but no decision \ - reached.", - vec![format!( - "sum by ({}) (increase({}[10m]))", + format!( + "The number of times consensus has timed out by type ({DEFAULT_DURATION} window). \n- \ + TimeoutPropose: as proposer, didn’t finish building in time; as validator, either \ + didn’t receive the proposal or didn’t finish validation in time.\n- TimeoutPrevote: \ + the node voted and received a quorum of prevotes, but not on the same value.\n- \ + TimeoutPrecommit: didn’t finish validation but quorum of precommits received, or it \ + finished but no decision reached." + ), + sum_by_label( + &CONSENSUS_TIMEOUTS, LABEL_NAME_TIMEOUT_TYPE, - CONSENSUS_TIMEOUTS.get_name_with_filter() - )], + DisplayMethod::Increase(DEFAULT_DURATION), + false, + ), PanelType::TimeSeries, ) + .with_log_query("Applying Timeout") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } + fn get_panel_consensus_l2_gas_price() -> Panel { Panel::new( "L2 Gas Price (GFri)", "L2 gas price in GFri calculated in an accepted proposal", - vec![format!("{} / 1e9", CONSENSUS_L2_GAS_PRICE.get_name_with_filter())], - PanelType::TimeSeries, - ) -} -fn get_panel_consensus_num_connected_peers() -> Panel { - Panel::new( - "Number of Connected Peers", - "The number of connected peers in Consensus P2P", - vec![CONSENSUS_NUM_CONNECTED_PEERS.get_name_with_filter().to_string()], - PanelType::TimeSeries, - ) -} -fn get_panel_consensus_votes_num_sent_messages() -> Panel { - Panel::new( - "Consensus Votes Number of Sent Messages", - "The increase in the number of vote messages sent by consensus p2p (10m window)", - vec![format!( - "increase({}[10m])", - CONSENSUS_VOTES_NUM_SENT_MESSAGES.get_name_with_filter() - )], - PanelType::TimeSeries, - ) -} -fn get_panel_consensus_votes_num_received_messages() -> Panel { - Panel::new( - "Consensus Votes Number of Received Messages", - "The increase in the number of vote messages received by consensus p2p (10m window)", - vec![format!( - "increase({}[10m])", - CONSENSUS_VOTES_NUM_RECEIVED_MESSAGES.get_name_with_filter() - )], - PanelType::TimeSeries, - ) -} -fn get_panel_consensus_proposals_num_sent_messages() -> Panel { - Panel::new( - "Consensus Proposals Number of Sent Messages", - "The increase in the number of proposal messages sent by consensus p2p (10m window)", - vec![format!( - "increase({}[10m])", - CONSENSUS_PROPOSALS_NUM_SENT_MESSAGES.get_name_with_filter() - )], - PanelType::TimeSeries, - ) -} -fn get_panel_consensus_proposals_num_received_messages() -> Panel { - Panel::new( - "Consensus Proposals Number of Received Messages", - "The increase in the number of proposal messages received by consensus p2p (10m window)", - vec![format!( - "increase({}[10m])", - CONSENSUS_PROPOSALS_NUM_RECEIVED_MESSAGES.get_name_with_filter() - )], - PanelType::TimeSeries, - ) -} -fn get_panel_consensus_conflicting_votes() -> Panel { - Panel::new( - "Consensus Conflicting Votes", - "The increase in the number of conflicting votes (12h window)", - vec![format!("increase({}[12h])", CONSENSUS_CONFLICTING_VOTES.get_name_with_filter())], + format!("{} / 1e9", CONSENSUS_L2_GAS_PRICE.get_name_with_filter()), PanelType::TimeSeries, ) } + fn get_panel_cende_last_prepared_blob_block_number() -> Panel { Panel::new( "Last Prepared Blob Block Number", "The block number that is ready to be sent to Cende in the next height", - vec![CENDE_LAST_PREPARED_BLOB_BLOCK_NUMBER.get_name_with_filter().to_string()], + CENDE_LAST_PREPARED_BLOB_BLOCK_NUMBER.get_name_with_filter().to_string(), PanelType::Stat, ) .with_log_query("Blob for block number") } + fn get_panel_cende_write_prev_height_blob_latency() -> Panel { - Panel::new( + Panel::from_hist( + &CENDE_WRITE_PREV_HEIGHT_BLOB_LATENCY, "Write Blob Latency", "The time it takes to write the blob to Cende", - // TODO(Dafna): add an helper function to generate a vector of histogram expressions, to be - // used everywhere - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - CENDE_WRITE_PREV_HEIGHT_BLOB_LATENCY.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } + fn get_panel_cende_write_blob_success() -> Panel { let query_expression = [ "\"Blob for block number\"", @@ -291,72 +305,154 @@ fn get_panel_cende_write_blob_success() -> Panel { Panel::new( "Write Blob Success", - "The number of successful blob writes to Cende (10m window)", - vec![format!("increase({}[10m])", CENDE_WRITE_BLOB_SUCCESS.get_name_with_filter())], + format!("The number of successful blob writes to Cende ({DEFAULT_DURATION} window)"), + increase(&CENDE_WRITE_BLOB_SUCCESS, DEFAULT_DURATION), PanelType::TimeSeries, ) .with_log_query(query_expression) } -pub(crate) fn get_panel_cende_write_blob_failure() -> Panel { + +fn get_panel_cende_write_blob_failure() -> Panel { Panel::new( "Write Blob Failure by Reason", - "The number of failed blob writes to Cende (10m window)", - vec![format!( - "sum by ({}) (increase({}[10m]))", + format!("The number of failed blob writes to Cende ({} window)", DEFAULT_DURATION), + sum_by_label( + &CENDE_WRITE_BLOB_FAILURE, LABEL_CENDE_FAILURE_REASON, - CENDE_WRITE_BLOB_FAILURE.get_name_with_filter() - )], + DisplayMethod::Increase(DEFAULT_DURATION), + false, + ), PanelType::TimeSeries, ) .with_log_query("CENDE_FAILURE") } + fn get_panel_cende_write_preconfirmed_block() -> Panel { Panel::new( "Write Preconfirmed Block Success", - "The number of successful writes to Cende for preconfirmed blocks (10m window). Each \ - preconfirmed block may involve multiple writes.", - vec![format!("increase({}[10m])", PRECONFIRMED_BLOCK_WRITTEN.get_name_with_filter())], + format!( + "The number of successful writes to Cende for preconfirmed blocks ({DEFAULT_DURATION} \ + window). Each preconfirmed block may involve multiple writes.", + ), + increase(&PRECONFIRMED_BLOCK_WRITTEN, DEFAULT_DURATION), PanelType::TimeSeries, ) + .with_log_query("write_pre_confirmed_block request succeeded.") +} + +fn get_panel_consensus_num_connected_peers() -> Panel { + Panel::new( + "Number of Connected Peers", + "The number of connected peers in Consensus P2P", + CONSENSUS_NUM_CONNECTED_PEERS.get_name_with_filter().to_string(), + PanelType::Stat, + ) +} + +fn get_panel_consensus_votes_num_sent_messages() -> Panel { + Panel::new( + "Consensus Votes Number of Sent Messages", + "The increase in the number of vote messages sent by consensus p2p (over the selected \ + time range)", + increase(&CONSENSUS_VOTES_NUM_SENT_MESSAGES, "$__range"), + PanelType::Stat, + ) +} + +fn get_panel_consensus_votes_num_received_messages() -> Panel { + Panel::new( + "Consensus Votes Number of Received Messages", + "The increase in the number of vote messages received by consensus p2p (over the selected \ + time range)", + increase(&CONSENSUS_VOTES_NUM_RECEIVED_MESSAGES, "$__range"), + PanelType::Stat, + ) +} + +fn get_panel_consensus_proposals_num_sent_messages() -> Panel { + Panel::new( + "Consensus Proposals Number of Sent Messages", + "The increase in the number of proposal messages sent by consensus p2p (over the selected \ + time range)", + increase(&CONSENSUS_PROPOSALS_NUM_SENT_MESSAGES, "$__range"), + PanelType::Stat, + ) +} + +fn get_panel_consensus_proposals_num_received_messages() -> Panel { + Panel::new( + "Consensus Proposals Number of Received Messages", + "The increase in the number of proposal messages received by consensus p2p (over the \ + selected time range)", + increase(&CONSENSUS_PROPOSALS_NUM_RECEIVED_MESSAGES, "$__range"), + PanelType::Stat, + ) +} + +fn get_panel_consensus_conflicting_votes() -> Panel { + Panel::new( + "Consensus Conflicting Votes", + "The increase in the number of conflicting votes (over the selected time range)", + increase(&CONSENSUS_CONFLICTING_VOTES, "$__range"), + PanelType::Stat, + ) } fn get_panel_consensus_network_events_by_type() -> Panel { Panel::new( - CONSENSUS_NETWORK_EVENTS.get_name(), - CONSENSUS_NETWORK_EVENTS.get_description(), - vec![format!( - "sum by ({}) ({})", + "Consensus Network Events By Type", + "Network events received by consensus p2p, by event type (over the selected time range)", + sum_by_label( + &CONSENSUS_NETWORK_EVENTS, LABEL_NAME_EVENT_TYPE, - CONSENSUS_NETWORK_EVENTS.get_name_with_filter() - )], - PanelType::TimeSeries, + DisplayMethod::Increase(RANGE_DURATION), + true, + ), + PanelType::Stat, ) } fn get_panel_consensus_votes_dropped_messages_by_reason() -> Panel { Panel::new( - CONSENSUS_VOTES_NUM_DROPPED_MESSAGES.get_name(), - CONSENSUS_VOTES_NUM_DROPPED_MESSAGES.get_description(), - vec![format!( - "sum by ({}) ({})", + "Consensus Votes Dropped Messages By Reason", + "The number of dropped consensus votes messages, by reason (over the selected time range)", + sum_by_label( + &CONSENSUS_VOTES_NUM_DROPPED_MESSAGES, LABEL_NAME_BROADCAST_DROP_REASON, - CONSENSUS_VOTES_NUM_DROPPED_MESSAGES.get_name_with_filter() - )], - PanelType::TimeSeries, + DisplayMethod::Increase(RANGE_DURATION), + true, + ), + PanelType::Stat, ) } fn get_panel_consensus_proposals_dropped_messages_by_reason() -> Panel { Panel::new( - CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES.get_name(), - CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES.get_description(), - vec![format!( - "sum by ({}) ({})", + "Consensus Proposals Dropped Messages By Reason", + "The number of dropped consensus proposals messages, by reason (over the selected time \ + range)", + sum_by_label( + &CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES, LABEL_NAME_BROADCAST_DROP_REASON, - CONSENSUS_PROPOSALS_NUM_DROPPED_MESSAGES.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + true, + ), + PanelType::Stat, + ) +} + +fn get_panel_consensus_decisions_reached_as_proposer() -> Panel { + Panel::new( + "Consensus Decisions Reached As Proposer", + format!( + "The number of rounds with decision reached where this node is the proposer \ + ({DEFAULT_DURATION} window)", + ), + increase(&CONSENSUS_DECISIONS_REACHED_AS_PROPOSER, DEFAULT_DURATION), PanelType::TimeSeries, ) + .with_log_query("\"Building proposal\" OR \"BATCHER_FIN_PROPOSER\"") + .with_log_comment(CONSENSUS_KEY_EVENTS_LOG_QUERY) } pub(crate) fn get_consensus_row() -> Row { @@ -365,8 +461,11 @@ pub(crate) fn get_consensus_row() -> Row { vec![ get_panel_consensus_block_number(), get_panel_consensus_round(), - get_panel_consensus_block_number_diff_from_sync(), + get_panel_consensus_round_advanced(), get_panel_consensus_block_time_avg(), + get_panel_consensus_round_above_zero(), + get_panel_consensus_block_number_diff_from_sync(), + get_panel_consensus_decisions_reached_as_proposer(), get_panel_consensus_decisions_reached_by_consensus(), get_panel_consensus_decisions_reached_by_sync(), get_panel_consensus_build_proposal_total(), diff --git a/crates/apollo_dashboard/src/panels/gateway.rs b/crates/apollo_dashboard/src/panels/gateway.rs index c01181d3986..9e5726911ca 100644 --- a/crates/apollo_dashboard/src/panels/gateway.rs +++ b/crates/apollo_dashboard/src/panels/gateway.rs @@ -4,25 +4,28 @@ use apollo_gateway::metrics::{ GATEWAY_TRANSACTIONS_FAILED, GATEWAY_TRANSACTIONS_RECEIVED, GATEWAY_TRANSACTIONS_SENT_TO_MEMPOOL, - GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_MICROS, GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_OPERATIONS, + GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_TIME, GATEWAY_VALIDATE_TX_LATENCY, LABEL_NAME_ADD_TX_FAILURE_REASON, LABEL_NAME_SOURCE, LABEL_NAME_TX_TYPE as GATEWAY_LABEL_NAME_TX_TYPE, }; +use apollo_metrics::MetricCommon; -use crate::dashboard::{Panel, PanelType, Row, Unit, HISTOGRAM_QUANTILES, HISTOGRAM_TIME_RANGE}; +use crate::dashboard::{Panel, PanelType, Row, Unit}; +use crate::query_builder::{sum_by_label, DisplayMethod, RANGE_DURATION}; fn get_panel_gateway_transactions_received_by_type() -> Panel { Panel::new( "Transactions Received by Type", "The number of transactions received by type (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range])) ", + sum_by_label( + &GATEWAY_TRANSACTIONS_RECEIVED, GATEWAY_LABEL_NAME_TX_TYPE, - GATEWAY_TRANSACTIONS_RECEIVED.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + false, + ), PanelType::Stat, ) .with_log_query("\"Processing tx\"") @@ -32,11 +35,12 @@ fn get_panel_gateway_transactions_received_by_source() -> Panel { Panel::new( "Transactions Received by Source", "The number of transactions received by source (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range])) ", + sum_by_label( + &GATEWAY_TRANSACTIONS_RECEIVED, LABEL_NAME_SOURCE, - GATEWAY_TRANSACTIONS_RECEIVED.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + false, + ), PanelType::Stat, ) .with_log_query("\"Processing tx\" AND \"is_p2p=\"") @@ -46,76 +50,64 @@ fn get_panel_gateway_transactions_received_rate() -> Panel { Panel::new( "Gateway Transactions Received Rate (TPS)", "The rate of transactions received by the gateway (1m window)", - vec![format!( + format!( "sum(rate({}[1m])) or vector(0)", GATEWAY_TRANSACTIONS_RECEIVED.get_name_with_filter() - )], + ), PanelType::TimeSeries, ) } fn get_panel_gateway_add_tx_latency() -> Panel { - // TODO(Asmaa): refactor Panel::from_hist to accept custom name and description parameters. - Panel::new( + Panel::from_hist( + &GATEWAY_ADD_TX_LATENCY, "Add Tx Latency", "The time it takes the gateway to add a transaction to the mempool", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - GATEWAY_ADD_TX_LATENCY.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } fn get_panel_gateway_validate_tx_latency() -> Panel { - Panel::new( + Panel::from_hist( + &GATEWAY_VALIDATE_TX_LATENCY, "Validate Tx Latency", "The time it takes to validate a transaction", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - GATEWAY_VALIDATE_TX_LATENCY.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } -fn get_panel_gateway_add_tx_failure_by_reason() -> Panel { +pub(crate) fn get_panel_gateway_add_tx_failure_by_reason() -> Panel { Panel::new( "Transactions Failed by Reason", "The number of transactions failed by reason (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range])) > 0", + sum_by_label( + &GATEWAY_ADD_TX_FAILURE, LABEL_NAME_ADD_TX_FAILURE_REASON, - GATEWAY_ADD_TX_FAILURE.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + true, + ), PanelType::Stat, ) } -pub(crate) fn get_panel_gateway_transactions_failure_rate() -> Panel { +fn get_panel_gateway_transactions_failure_rate() -> Panel { + let sum_failed = sum_by_label( + &GATEWAY_TRANSACTIONS_FAILED, + GATEWAY_LABEL_NAME_TX_TYPE, + DisplayMethod::Increase(RANGE_DURATION), + false, + ); + let sum_received = sum_by_label( + &GATEWAY_TRANSACTIONS_RECEIVED, + GATEWAY_LABEL_NAME_TX_TYPE, + DisplayMethod::Increase(RANGE_DURATION), + false, + ); Panel::new( "Transaction Failure Rate by Type", "The rate of failed transactions vs received transactions by type (over the selected time \ range)", - vec![format!( - "(sum by ({}) (increase({}[$__range])) / sum by ({}) (increase({}[$__range])))", - GATEWAY_LABEL_NAME_TX_TYPE, - GATEWAY_TRANSACTIONS_FAILED.get_name_with_filter(), - GATEWAY_LABEL_NAME_TX_TYPE, - GATEWAY_TRANSACTIONS_RECEIVED.get_name_with_filter() - )], + format!("({sum_failed} / {sum_received})",), PanelType::Stat, ) .with_unit(Unit::PercentUnit) @@ -125,21 +117,31 @@ fn get_panel_gateway_transactions_sent_to_mempool() -> Panel { Panel::new( "Transactions Sent to Mempool by Type", "The number of transactions sent to mempool by type (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range]))", + sum_by_label( + &GATEWAY_TRANSACTIONS_SENT_TO_MEMPOOL, GATEWAY_LABEL_NAME_TX_TYPE, - GATEWAY_TRANSACTIONS_SENT_TO_MEMPOOL.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + false, + ), PanelType::Stat, ) } -fn get_panel_gateway_validate_stateful_tx_storage_micros() -> Panel { - Panel::from_hist(&GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_MICROS, PanelType::TimeSeries) +fn get_panel_gateway_validate_stateful_tx_storage_time() -> Panel { + Panel::from_hist( + &GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_TIME, + "Gateway Validate Stateful Tx Storage Access Time", + "Total time spent in storage operations during stateful tx validation", + ) + .with_unit(Unit::Seconds) } fn get_panel_gateway_validate_stateful_tx_storage_operations() -> Panel { - Panel::from_hist(&GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_OPERATIONS, PanelType::TimeSeries) + Panel::from_hist( + &GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_OPERATIONS, + "Gateway Validate Stateful Tx Storage Operations", + "Total number of storage operations during stateful tx validation", + ) } pub(crate) fn get_gateway_row() -> Row { @@ -154,7 +156,7 @@ pub(crate) fn get_gateway_row() -> Row { get_panel_gateway_transactions_failure_rate(), get_panel_gateway_add_tx_failure_by_reason(), get_panel_gateway_transactions_sent_to_mempool(), - get_panel_gateway_validate_stateful_tx_storage_micros(), + get_panel_gateway_validate_stateful_tx_storage_time(), get_panel_gateway_validate_stateful_tx_storage_operations(), ], ) diff --git a/crates/apollo_dashboard/src/panels/http_server.rs b/crates/apollo_dashboard/src/panels/http_server.rs index 84bd07b42ae..0fccf45b333 100644 --- a/crates/apollo_dashboard/src/panels/http_server.rs +++ b/crates/apollo_dashboard/src/panels/http_server.rs @@ -6,28 +6,33 @@ use apollo_http_server::metrics::{ ADDED_TRANSACTIONS_TOTAL, HTTP_SERVER_ADD_TX_LATENCY, }; +use apollo_metrics::MetricCommon; -use crate::dashboard::{Panel, PanelType, Row, Unit, HISTOGRAM_QUANTILES, HISTOGRAM_TIME_RANGE}; +use crate::dashboard::{Panel, PanelType, Row, Unit}; +use crate::query_builder::{increase, DEFAULT_DURATION}; fn get_panel_total_transactions_received() -> Panel { Panel::new( "Transactions Received", - "Number of transactions received (10m window)", - vec![format!("increase({}[10m])", ADDED_TRANSACTIONS_TOTAL.get_name_with_filter())], + format!("Number of transactions received ({DEFAULT_DURATION} window)"), + increase(&ADDED_TRANSACTIONS_TOTAL, DEFAULT_DURATION), PanelType::TimeSeries, ) .with_log_query("\"ADD_TX_START\"") } fn get_panel_transaction_success_rate() -> Panel { + // TODO(MatanL): use Panel::ratio_time_series Panel::new( "Transaction Success Rate", - "The ratio of transactions successfully added to the gateway (10m window)", - vec![format!( - "increase({}[10m]) / (increase({}[10m]) + increase({}[10m]))", - ADDED_TRANSACTIONS_SUCCESS.get_name_with_filter(), - ADDED_TRANSACTIONS_SUCCESS.get_name_with_filter(), - ADDED_TRANSACTIONS_FAILURE.get_name_with_filter(), - )], + format!( + "The ratio of transactions successfully added to the gateway ({DEFAULT_DURATION} \ + window)", + ), + format!( + "{s} / ({s} + {f})", + s = increase(&ADDED_TRANSACTIONS_SUCCESS, DEFAULT_DURATION), + f = increase(&ADDED_TRANSACTIONS_FAILURE, DEFAULT_DURATION), + ), PanelType::TimeSeries, ) .with_unit(Unit::PercentUnit) @@ -37,24 +42,15 @@ pub(crate) fn get_panel_http_server_transactions_received_rate() -> Panel { Panel::new( "HTTP Server Transactions Received Rate (TPS)", "The rate of transactions received by the HTTP Server (1m window)", - vec![format!("rate({}[1m])", ADDED_TRANSACTIONS_TOTAL.get_name_with_filter())], + format!("rate({}[1m])", ADDED_TRANSACTIONS_TOTAL.get_name_with_filter()), PanelType::TimeSeries, ) } fn get_panel_http_add_tx_latency() -> Panel { - Panel::new( + Panel::from_hist( + &HTTP_SERVER_ADD_TX_LATENCY, "HTTP Server Add Tx Latency", "The time it takes to add a transaction to the HTTP Server", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - HTTP_SERVER_ADD_TX_LATENCY.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } diff --git a/crates/apollo_dashboard/src/panels/l1_gas_price.rs b/crates/apollo_dashboard/src/panels/l1_gas_price.rs index 89796bb324c..32633f12493 100644 --- a/crates/apollo_dashboard/src/panels/l1_gas_price.rs +++ b/crates/apollo_dashboard/src/panels/l1_gas_price.rs @@ -11,24 +11,37 @@ use apollo_l1_gas_price::metrics::{ L1_GAS_PRICE_SCRAPER_SUCCESS_COUNT, }; use apollo_l1_gas_price_types::DEFAULT_ETH_TO_FRI_RATE; +use apollo_metrics::MetricCommon; -use crate::dashboard::{Panel, PanelType, Row}; +use crate::dashboard::{get_time_since_last_increase_expr, Panel, PanelType, Row, Unit}; +use crate::query_builder::{increase, DEFAULT_DURATION}; fn get_panel_eth_to_strk_error_count() -> Panel { Panel::new( "ETH→STRK Rate Query Error Count", - "The number of times the ETH→STRK rate query failed (10m window)", - vec![format!("increase({}[10m])", ETH_TO_STRK_ERROR_COUNT.get_name_with_filter())], + format!("The number of times the ETH→STRK rate query failed ({DEFAULT_DURATION} window)"), + increase(Ð_TO_STRK_ERROR_COUNT, DEFAULT_DURATION), PanelType::TimeSeries, ) } +fn get_panel_eth_to_strk_seconds_since_last_successful_update() -> Panel { + Panel::new( + "Seconds since last successful ETH→STRK rate update", + "The number of seconds since the last successful ETH→STRK rate update (assuming there was \ + an update in the last 12 hours)", + get_time_since_last_increase_expr(Ð_TO_STRK_SUCCESS_COUNT.get_name_with_filter()), + PanelType::TimeSeries, + ) + .with_unit(Unit::Seconds) +} + fn get_panel_eth_to_strk_success_count() -> Panel { Panel::new( "ETH→STRK Rate Query Success (binary)", "Indicates whether the ETH→STRK rate query succeeded (1m window) \nExpected to be 1 every \ 15 minutes.", - vec![format!("changes({}[1m])", ETH_TO_STRK_SUCCESS_COUNT.get_name_with_filter())], + format!("changes({}[1m])", ETH_TO_STRK_SUCCESS_COUNT.get_name_with_filter()), PanelType::TimeSeries, ) .with_log_query("Caching conversion rate for timestamp") @@ -36,9 +49,9 @@ fn get_panel_eth_to_strk_success_count() -> Panel { fn get_panel_eth_to_strk_rate() -> Panel { Panel::new( - ETH_TO_STRK_RATE.get_name(), + "ETH→STRK rate", format!("ETH→STRK rate (divided by DEFAULT_ETH_TO_FRI_RATE={DEFAULT_ETH_TO_FRI_RATE})"), - vec![format!("{} / {}", ETH_TO_STRK_RATE.get_name_with_filter(), DEFAULT_ETH_TO_FRI_RATE)], + format!("{} / {}", ETH_TO_STRK_RATE.get_name_with_filter(), DEFAULT_ETH_TO_FRI_RATE), PanelType::TimeSeries, ) .with_log_query("Caching conversion rate for timestamp") @@ -47,12 +60,11 @@ fn get_panel_eth_to_strk_rate() -> Panel { fn get_panel_l1_gas_price_provider_insufficient_history() -> Panel { Panel::new( "L1 Gas Price Provider Insufficient History", - "The number of times the L1 gas price provider calculated an average with too few blocks \ - (10m window)", - vec![format!( - "increase({}[10m])", - L1_GAS_PRICE_PROVIDER_INSUFFICIENT_HISTORY.get_name_with_filter() - )], + format!( + "The number of times the L1 gas price provider calculated an average with too few \ + blocks ({DEFAULT_DURATION} window)", + ), + increase(&L1_GAS_PRICE_PROVIDER_INSUFFICIENT_HISTORY, DEFAULT_DURATION), PanelType::TimeSeries, ) .with_log_query("Not enough history to calculate the mean gas price.") @@ -61,12 +73,11 @@ fn get_panel_l1_gas_price_provider_insufficient_history() -> Panel { fn get_panel_l1_gas_price_scraper_success_count() -> Panel { Panel::new( "L1 Gas Price Scraper Success Count", - "The number of times the L1 gas price scraper successfully scraped and updated gas prices \ - (10m window)", - vec![format!( - "increase({}[10m])", - L1_GAS_PRICE_SCRAPER_SUCCESS_COUNT.get_name_with_filter() - )], + format!( + "The number of times the L1 gas price scraper successfully scraped and updated gas \ + prices ({DEFAULT_DURATION} window)", + ), + increase(&L1_GAS_PRICE_SCRAPER_SUCCESS_COUNT, DEFAULT_DURATION), PanelType::TimeSeries, ) } @@ -74,12 +85,11 @@ fn get_panel_l1_gas_price_scraper_success_count() -> Panel { fn get_panel_l1_gas_price_scraper_baselayer_error_count() -> Panel { Panel::new( "L1 Gas Price Scraper Base Layer Error Count", - "The number of times the L1 gas price scraper encountered an error while scraping the \ - base layer (10m window)", - vec![format!( - "increase({}[10m])", - L1_GAS_PRICE_SCRAPER_BASELAYER_ERROR_COUNT.get_name_with_filter() - )], + format!( + "The number of times the L1 gas price scraper encountered an error while scraping the \ + base layer ({DEFAULT_DURATION} window)", + ), + increase(&L1_GAS_PRICE_SCRAPER_BASELAYER_ERROR_COUNT, DEFAULT_DURATION), PanelType::TimeSeries, ) } @@ -87,21 +97,33 @@ fn get_panel_l1_gas_price_scraper_baselayer_error_count() -> Panel { fn get_panel_l1_gas_price_scraper_reorg_detected() -> Panel { Panel::new( "L1 Gas Price Scraper Reorg Detected", - "The number of times the L1 gas price scraper detected a reorganization in the base layer \ - (10m window)", - vec![format!( - "increase({}[10m])", - L1_GAS_PRICE_SCRAPER_REORG_DETECTED.get_name_with_filter() - )], + format!( + "The number of times the L1 gas price scraper detected a reorganization in the base \ + layer ({DEFAULT_DURATION} window)", + ), + increase(&L1_GAS_PRICE_SCRAPER_REORG_DETECTED, DEFAULT_DURATION), + PanelType::TimeSeries, + ) +} + +fn get_panel_l1_gas_price_scraper_seconds_since_last_successful_scrape() -> Panel { + Panel::new( + "Seconds since last successful L1 gas price scrape", + "The number of seconds since the last successful scrape of the L1 gas price scraper \ + (assuming there was a scrape in the last 12 hours)", + get_time_since_last_increase_expr( + &L1_GAS_PRICE_SCRAPER_SUCCESS_COUNT.get_name_with_filter(), + ), PanelType::TimeSeries, ) + .with_unit(Unit::Seconds) } fn get_panel_l1_gas_price_scraper_latest_scraped_block() -> Panel { Panel::new( "L1 Gas Price Scraper Latest Scraped Block", "The latest block number that the L1 gas price scraper has scraped", - vec![format!("{}", L1_GAS_PRICE_SCRAPER_LATEST_SCRAPED_BLOCK.get_name_with_filter())], + L1_GAS_PRICE_SCRAPER_LATEST_SCRAPED_BLOCK.get_name_with_filter(), PanelType::Stat, ) } @@ -110,7 +132,7 @@ fn get_panel_l1_gas_price_latest_mean_value() -> Panel { Panel::new( "L1 Gas Price Latest Mean Value", "The latest L1 gas price, calculated as an average by the provider client", - vec![format!("{}", L1_GAS_PRICE_LATEST_MEAN_VALUE.get_name_with_filter())], + L1_GAS_PRICE_LATEST_MEAN_VALUE.get_name_with_filter(), PanelType::TimeSeries, ) } @@ -119,7 +141,7 @@ fn get_panel_l1_data_gas_price_latest_mean_value() -> Panel { Panel::new( "L1 Data Gas Price Latest Mean Value", "The latest L1 data gas price, calculated as an average by the provider client", - vec![format!("{}", L1_DATA_GAS_PRICE_LATEST_MEAN_VALUE.get_name_with_filter())], + L1_DATA_GAS_PRICE_LATEST_MEAN_VALUE.get_name_with_filter(), PanelType::TimeSeries, ) } @@ -128,6 +150,8 @@ pub(crate) fn get_l1_gas_price_row() -> Row { Row::new( "ETH→STRK Rate & L1 Gas Price", vec![ + get_panel_l1_gas_price_scraper_seconds_since_last_successful_scrape(), + get_panel_eth_to_strk_seconds_since_last_successful_update(), get_panel_eth_to_strk_success_count(), get_panel_eth_to_strk_error_count(), get_panel_eth_to_strk_rate(), diff --git a/crates/apollo_dashboard/src/panels/l1_provider.rs b/crates/apollo_dashboard/src/panels/l1_provider.rs index 49efa399896..aab561df0cc 100644 --- a/crates/apollo_dashboard/src/panels/l1_provider.rs +++ b/crates/apollo_dashboard/src/panels/l1_provider.rs @@ -1,30 +1,32 @@ use apollo_l1_provider::metrics::{ L1_MESSAGE_SCRAPER_BASELAYER_ERROR_COUNT, L1_MESSAGE_SCRAPER_REORG_DETECTED, - L1_MESSAGE_SCRAPER_SECONDS_SINCE_LAST_SUCCESSFUL_SCRAPE, L1_MESSAGE_SCRAPER_SUCCESS_COUNT, }; +use apollo_metrics::MetricCommon; -use crate::dashboard::{Panel, PanelType, Row}; +use crate::dashboard::{get_time_since_last_increase_expr, Panel, PanelType, Row, Unit}; +use crate::query_builder::{increase, DEFAULT_DURATION}; fn get_panel_l1_message_scraper_success_count() -> Panel { Panel::new( "L1 Message Scraper Success Count", - "The increase in the number of times the L1 message scraper successfully scraped messages \ - (10m window)", - vec![format!("increase({}[10m])", L1_MESSAGE_SCRAPER_SUCCESS_COUNT.get_name_with_filter())], + format!( + "The increase in the number of times the L1 message scraper successfully scraped \ + messages ({DEFAULT_DURATION} window)", + ), + increase(&L1_MESSAGE_SCRAPER_SUCCESS_COUNT, DEFAULT_DURATION), PanelType::TimeSeries, ) } fn get_panel_l1_message_scraper_baselayer_error_count() -> Panel { Panel::new( "L1 Message Scraper Base Layer Error Count", - "The increase in the number of times the L1 message scraper encountered an error while \ - scraping the base layer (10m window)", - vec![format!( - "increase({}[10m])", - L1_MESSAGE_SCRAPER_BASELAYER_ERROR_COUNT.get_name_with_filter() - )], + format!( + "The increase in the number of times the L1 message scraper encountered an error \ + while scraping the base layer ({DEFAULT_DURATION} window)", + ), + increase(&L1_MESSAGE_SCRAPER_BASELAYER_ERROR_COUNT, DEFAULT_DURATION), PanelType::TimeSeries, ) } @@ -32,18 +34,20 @@ fn get_panel_l1_message_scraper_reorg_detected() -> Panel { Panel::new( "L1 Message Scraper Reorg Detected", "The increase in the number of times the L1 message scraper detected a reorg (12h window)", - vec![format!( - "increase({}[12h])", - L1_MESSAGE_SCRAPER_REORG_DETECTED.get_name_with_filter() - )], + increase(&L1_MESSAGE_SCRAPER_REORG_DETECTED, "12h"), PanelType::TimeSeries, ) } fn get_panel_l1_message_scraper_seconds_since_last_successful_scrape() -> Panel { - Panel::from_gauge( - &L1_MESSAGE_SCRAPER_SECONDS_SINCE_LAST_SUCCESSFUL_SCRAPE, + Panel::new( + "Seconds since last successful l1 event scrape", + "The number of seconds since the last successful scrape of the L1 message scraper \ + (assuming there was a scrape in the last 12 hours)", + get_time_since_last_increase_expr(&L1_MESSAGE_SCRAPER_SUCCESS_COUNT.get_name_with_filter()), PanelType::TimeSeries, ) + .with_unit(Unit::Seconds) + .with_log_query("BaseLayerError during scraping") } // TODO(noamsp): rename to l1_event_row diff --git a/crates/apollo_dashboard/src/panels/mempool.rs b/crates/apollo_dashboard/src/panels/mempool.rs index 3e8e86a217a..d1b346f8d5d 100644 --- a/crates/apollo_dashboard/src/panels/mempool.rs +++ b/crates/apollo_dashboard/src/panels/mempool.rs @@ -11,17 +11,25 @@ use apollo_mempool::metrics::{ TRANSACTION_TIME_SPENT_UNTIL_BATCHED, TRANSACTION_TIME_SPENT_UNTIL_COMMITTED, }; +use apollo_metrics::MetricCommon; -use crate::dashboard::{Panel, PanelType, Row, Unit, HISTOGRAM_QUANTILES, HISTOGRAM_TIME_RANGE}; +use crate::dashboard::{Panel, PanelType, Row, Unit}; +use crate::query_builder::{ + increase, + sum_by_label, + DisplayMethod, + DEFAULT_DURATION, + RANGE_DURATION, +}; fn get_panel_mempool_transactions_received_rate() -> Panel { Panel::new( "Mempool Transactions Received Rate (TPS)", "The rate of transactions received by the mempool (1m window)", - vec![format!( + format!( "sum(rate({}[1m])) or vector(0)", MEMPOOL_TRANSACTIONS_RECEIVED.get_name_with_filter() - )], + ), PanelType::TimeSeries, ) .with_log_query("Adding transaction to mempool") @@ -29,8 +37,8 @@ fn get_panel_mempool_transactions_received_rate() -> Panel { fn get_panel_mempool_transactions_committed() -> Panel { Panel::new( "Transactions Committed", - "Number of transactions committed to a block (10m window)", - vec![format!("increase({}[10m])", MEMPOOL_TRANSACTIONS_COMMITTED.get_name_with_filter())], + format!("Number of transactions committed to a block ({DEFAULT_DURATION} window)"), + increase(&MEMPOOL_TRANSACTIONS_COMMITTED, DEFAULT_DURATION), PanelType::TimeSeries, ) } @@ -38,11 +46,12 @@ fn get_panel_mempool_transactions_dropped() -> Panel { Panel::new( "Dropped Transactions by Reason", "Number of transactions dropped from the mempool by reason (over the selected time range)", - vec![format!( - "sum by ({}) (increase({}[$__range]))", + sum_by_label( + &MEMPOOL_TRANSACTIONS_DROPPED, LABEL_NAME_DROP_REASON, - MEMPOOL_TRANSACTIONS_DROPPED.get_name_with_filter() - )], + DisplayMethod::Increase(RANGE_DURATION), + false, + ), PanelType::Stat, ) } @@ -50,7 +59,7 @@ fn get_panel_mempool_pool_size() -> Panel { Panel::new( "Pool Size (Num TXs)", "Number of all the transactions in the mempool", - vec![MEMPOOL_POOL_SIZE.get_name_with_filter().to_string()], + MEMPOOL_POOL_SIZE.get_name_with_filter().to_string(), PanelType::TimeSeries, ) } @@ -58,7 +67,7 @@ fn get_panel_mempool_priority_queue_size() -> Panel { Panel::new( "Prioritized Transactions", "Number of transactions prioritized for batching", - vec![MEMPOOL_PRIORITY_QUEUE_SIZE.get_name_with_filter().to_string()], + MEMPOOL_PRIORITY_QUEUE_SIZE.get_name_with_filter().to_string(), PanelType::TimeSeries, ) } @@ -66,7 +75,7 @@ fn get_panel_mempool_pending_queue_size() -> Panel { Panel::new( "Pending Transactions", "Number of transactions eligible for batching but below the gas price threshold", - vec![MEMPOOL_PENDING_QUEUE_SIZE.get_name_with_filter().to_string()], + MEMPOOL_PENDING_QUEUE_SIZE.get_name_with_filter().to_string(), PanelType::TimeSeries, ) } @@ -74,7 +83,7 @@ fn get_panel_mempool_total_size_in_bytes() -> Panel { Panel::new( "Mempool Size (Data)", "Size of the transactions in the mempool", - vec![MEMPOOL_TOTAL_SIZE_BYTES.get_name_with_filter().to_string()], + MEMPOOL_TOTAL_SIZE_BYTES.get_name_with_filter().to_string(), PanelType::TimeSeries, ) .with_unit(Unit::Bytes) @@ -83,41 +92,23 @@ fn get_panel_mempool_delayed_declares_size() -> Panel { Panel::new( "Delayed Declare Transactions", "Number of delayed declare transactions", - vec![MEMPOOL_DELAYED_DECLARES_SIZE.get_name_with_filter().to_string()], + MEMPOOL_DELAYED_DECLARES_SIZE.get_name_with_filter().to_string(), PanelType::TimeSeries, ) } fn get_panel_mempool_transaction_time_spent_until_batched() -> Panel { - Panel::new( + Panel::from_hist( + &TRANSACTION_TIME_SPENT_UNTIL_BATCHED, "Transaction Time Spent in Mempool Until Batched", "The time a transaction spends in the mempool until it is batched (5m window)", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - TRANSACTION_TIME_SPENT_UNTIL_BATCHED.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } fn get_panel_mempool_transaction_time_spent_until_committed() -> Panel { - Panel::new( + Panel::from_hist( + &TRANSACTION_TIME_SPENT_UNTIL_COMMITTED, "Transaction Time Spent in Mempool Until Committed", "The time a transaction spends in the mempool until it is committed (5m window)", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - TRANSACTION_TIME_SPENT_UNTIL_COMMITTED.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } diff --git a/crates/apollo_dashboard/src/panels/mempool_p2p.rs b/crates/apollo_dashboard/src/panels/mempool_p2p.rs index 5509abbd2df..d215d5fab85 100644 --- a/crates/apollo_dashboard/src/panels/mempool_p2p.rs +++ b/crates/apollo_dashboard/src/panels/mempool_p2p.rs @@ -6,12 +6,14 @@ use apollo_mempool_p2p::metrics::{ MEMPOOL_P2P_NUM_RECEIVED_MESSAGES, MEMPOOL_P2P_NUM_SENT_MESSAGES, }; +use apollo_metrics::MetricCommon; use apollo_network::network_manager::metrics::{ LABEL_NAME_BROADCAST_DROP_REASON, LABEL_NAME_EVENT_TYPE, }; use crate::dashboard::{Panel, PanelType, Row}; +use crate::query_builder::{sum_by_label, DisplayMethod}; fn get_panel_mempool_p2p_num_connected_peers() -> Panel { Panel::from_gauge(&MEMPOOL_P2P_NUM_CONNECTED_PEERS, PanelType::TimeSeries) @@ -26,18 +28,18 @@ fn get_panel_mempool_p2p_num_received_messages() -> Panel { } fn get_panel_mempool_p2p_broadcasted_batch_size() -> Panel { - Panel::from_hist(&MEMPOOL_P2P_BROADCASTED_BATCH_SIZE, PanelType::TimeSeries) + Panel::from_hist( + &MEMPOOL_P2P_BROADCASTED_BATCH_SIZE, + "Mempool P2p Broadcasted Transaction Batch Size", + "The number of transactions in batches broadcast by the mempool p2p component", + ) } fn get_panel_mempool_p2p_network_events_by_type() -> Panel { Panel::new( MEMPOOL_P2P_NETWORK_EVENTS.get_name(), MEMPOOL_P2P_NETWORK_EVENTS.get_description(), - vec![format!( - "sum by ({}) ({})", - LABEL_NAME_EVENT_TYPE, - MEMPOOL_P2P_NETWORK_EVENTS.get_name_with_filter() - )], + sum_by_label(&MEMPOOL_P2P_NETWORK_EVENTS, LABEL_NAME_EVENT_TYPE, DisplayMethod::Raw, false), PanelType::TimeSeries, ) } @@ -46,11 +48,12 @@ fn get_panel_mempool_p2p_dropped_messages_by_reason() -> Panel { Panel::new( MEMPOOL_P2P_NUM_DROPPED_MESSAGES.get_name(), MEMPOOL_P2P_NUM_DROPPED_MESSAGES.get_description(), - vec![format!( - "sum by ({}) ({})", + sum_by_label( + &MEMPOOL_P2P_NUM_DROPPED_MESSAGES, LABEL_NAME_BROADCAST_DROP_REASON, - MEMPOOL_P2P_NUM_DROPPED_MESSAGES.get_name_with_filter() - )], + DisplayMethod::Raw, + false, + ), PanelType::TimeSeries, ) } diff --git a/crates/apollo_dashboard/src/panels/pod_metrics.rs b/crates/apollo_dashboard/src/panels/pod_metrics.rs index 67f7b54858f..859ce3a3e59 100644 --- a/crates/apollo_dashboard/src/panels/pod_metrics.rs +++ b/crates/apollo_dashboard/src/panels/pod_metrics.rs @@ -6,7 +6,7 @@ fn get_pod_memory_utilization_panel() -> Panel { Panel::new( "pod_memory_utilization", "Pod Memory Utilization", - vec![format!("container_memory_working_set_bytes{0}", metric_label_filter!())], + format!("container_memory_working_set_bytes{0}", metric_label_filter!()), PanelType::TimeSeries, ) } @@ -15,7 +15,7 @@ fn get_pod_disk_utilization_panel() -> Panel { Panel::new( "pod_disk_utilization", "Pod Disk Utilization", - vec![format!("kubelet_volume_stats_used_bytes{0}", metric_label_filter!())], + format!("kubelet_volume_stats_used_bytes{0}", metric_label_filter!()), PanelType::TimeSeries, ) } @@ -24,7 +24,7 @@ fn get_pod_cpu_utilization_panel() -> Panel { Panel::new( "pod_cpu_utilization", "Pod CPU Utilization", - vec![format!("container_cpu_usage_seconds_total{0}", metric_label_filter!())], + format!("container_cpu_usage_seconds_total{0}", metric_label_filter!()), PanelType::TimeSeries, ) } diff --git a/crates/apollo_dashboard/src/panels/reverts.rs b/crates/apollo_dashboard/src/panels/reverts.rs new file mode 100644 index 00000000000..c641a7cfc89 --- /dev/null +++ b/crates/apollo_dashboard/src/panels/reverts.rs @@ -0,0 +1,16 @@ +use apollo_consensus_manager::metrics::CONSENSUS_REVERTED_BATCHER_UP_TO_AND_INCLUDING; +use apollo_state_sync_metrics::metrics::STATE_SYNC_REVERTED_UP_TO_AND_INCLUDING; + +use crate::dashboard::{Panel, PanelType, Row}; + +fn get_panel_consensus_reverts() -> Panel { + Panel::from_gauge(&CONSENSUS_REVERTED_BATCHER_UP_TO_AND_INCLUDING, PanelType::Stat) +} + +fn get_panel_state_sync_reverts() -> Panel { + Panel::from_gauge(&STATE_SYNC_REVERTED_UP_TO_AND_INCLUDING, PanelType::Stat) +} + +pub(crate) fn get_reverts_row() -> Row { + Row::new("Reverts", vec![get_panel_consensus_reverts(), get_panel_state_sync_reverts()]) +} diff --git a/crates/apollo_dashboard/src/panels/sierra_compiler.rs b/crates/apollo_dashboard/src/panels/sierra_compiler.rs index 8fcaa240c09..81b65d375f7 100644 --- a/crates/apollo_dashboard/src/panels/sierra_compiler.rs +++ b/crates/apollo_dashboard/src/panels/sierra_compiler.rs @@ -1,52 +1,32 @@ use apollo_class_manager::metrics::{CLASS_SIZES, N_CLASSES}; use apollo_compile_to_casm::metrics::COMPILATION_DURATION; -use crate::dashboard::{Panel, PanelType, Row, Unit, HISTOGRAM_QUANTILES, HISTOGRAM_TIME_RANGE}; +use crate::dashboard::{Panel, PanelType, Row, Unit}; +use crate::query_builder::{sum_by_label, DisplayMethod, DEFAULT_DURATION}; fn get_panel_compilation_duration() -> Panel { - Panel::new( + Panel::from_hist( + &COMPILATION_DURATION, "Compile to Casm Compilation Duration", "Server-side compilation of Sierra to Casm duration", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - COMPILATION_DURATION.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } fn get_panel_n_classes() -> Panel { Panel::new( "Number of Classes", - "Number of classes, labeled by type (regular, deprecated)", - vec![format!( - "sum by ({}) (increase({}[10m]))", - "class_type", - N_CLASSES.get_name_with_filter() - )], + format!( + "Number of classes, labeled by type (regular, deprecated) ({DEFAULT_DURATION} window)" + ), + sum_by_label(&N_CLASSES, "class_type", DisplayMethod::Increase(DEFAULT_DURATION), false), PanelType::Stat, ) } fn get_panel_class_sizes() -> Panel { - Panel::new( + Panel::from_labeled_hist( + &CLASS_SIZES, "Class Sizes", "Size of the classes in bytes, labeled by type (sierra, casm, deprecated casm)", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le, class_object_type) \ - (rate({}[{HISTOGRAM_TIME_RANGE}])))", - CLASS_SIZES.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::MB) } diff --git a/crates/apollo_dashboard/src/panels/state_sync.rs b/crates/apollo_dashboard/src/panels/state_sync.rs index f2e7c693bc2..c54397a7b2f 100644 --- a/crates/apollo_dashboard/src/panels/state_sync.rs +++ b/crates/apollo_dashboard/src/panels/state_sync.rs @@ -1,8 +1,6 @@ +use apollo_metrics::MetricCommon; use apollo_state_sync_metrics::metrics::{ CENTRAL_SYNC_CENTRAL_BLOCK_MARKER, - P2P_SYNC_NUM_ACTIVE_INBOUND_SESSIONS, - P2P_SYNC_NUM_ACTIVE_OUTBOUND_SESSIONS, - P2P_SYNC_NUM_CONNECTED_PEERS, STATE_SYNC_BODY_MARKER, STATE_SYNC_CLASS_MANAGER_MARKER, STATE_SYNC_HEADER_LATENCY_SEC, @@ -10,25 +8,11 @@ use apollo_state_sync_metrics::metrics::{ use crate::dashboard::{Panel, PanelType, Row, Unit}; -// P2P panels - -fn get_panel_p2p_sync_num_connected_peers() -> Panel { - Panel::from_gauge(&P2P_SYNC_NUM_CONNECTED_PEERS, PanelType::Stat) -} -fn get_panel_p2p_sync_num_active_inbound_sessions() -> Panel { - Panel::from_gauge(&P2P_SYNC_NUM_ACTIVE_INBOUND_SESSIONS, PanelType::Stat) -} -fn get_panel_p2p_sync_num_active_outbound_sessions() -> Panel { - Panel::from_gauge(&P2P_SYNC_NUM_ACTIVE_OUTBOUND_SESSIONS, PanelType::Stat) -} - -// State Sync panels - fn get_panel_central_sync_central_block_marker() -> Panel { Panel::new( "Central Block Marker", "The first block that Central Starknet hasn't seen yet", - vec![CENTRAL_SYNC_CENTRAL_BLOCK_MARKER.get_name_with_filter().to_string()], + CENTRAL_SYNC_CENTRAL_BLOCK_MARKER.get_name_with_filter().to_string(), PanelType::Stat, ) } @@ -36,19 +20,19 @@ fn get_panel_state_sync_body_marker() -> Panel { Panel::new( "State Sync Body Marker", "The first block number for which the state sync component does not have a body", - vec![STATE_SYNC_BODY_MARKER.get_name_with_filter().to_string()], + STATE_SYNC_BODY_MARKER.get_name_with_filter().to_string(), PanelType::Stat, ) } -pub(crate) fn get_panel_state_sync_diff_from_central() -> Panel { +fn get_panel_state_sync_diff_from_central() -> Panel { Panel::new( "Sync Diff From Central", "The number of blocks that were not fully synced yet", - vec![format!( + format!( "{} - {}", CENTRAL_SYNC_CENTRAL_BLOCK_MARKER.get_name_with_filter(), STATE_SYNC_CLASS_MANAGER_MARKER.get_name_with_filter() - )], + ), PanelType::TimeSeries, ) } @@ -56,7 +40,7 @@ fn get_panel_state_sync_new_header_maturity() -> Panel { Panel::new( "Sync Block Age", "The time from a block’s timestamp until its header is synced through the feeder-gateway.", - vec![STATE_SYNC_HEADER_LATENCY_SEC.get_name_with_filter().to_string()], + STATE_SYNC_HEADER_LATENCY_SEC.get_name_with_filter().to_string(), PanelType::TimeSeries, ) .with_unit(Unit::Seconds) @@ -73,14 +57,3 @@ pub(crate) fn get_state_sync_row() -> Row { ], ) } - -pub(crate) fn get_state_sync_p2p_row() -> Row { - Row::new( - "StateSyncP2p", - vec![ - get_panel_p2p_sync_num_connected_peers(), - get_panel_p2p_sync_num_active_inbound_sessions(), - get_panel_p2p_sync_num_active_outbound_sessions(), - ], - ) -} diff --git a/crates/apollo_dashboard/src/panels/storage.rs b/crates/apollo_dashboard/src/panels/storage.rs index b42140ceadd..01adf2369c1 100644 --- a/crates/apollo_dashboard/src/panels/storage.rs +++ b/crates/apollo_dashboard/src/panels/storage.rs @@ -6,39 +6,21 @@ use apollo_storage::metrics::{ SYNC_STORAGE_OPEN_READ_TRANSACTIONS, }; -use crate::dashboard::{Panel, PanelType, Row, Unit, HISTOGRAM_QUANTILES, HISTOGRAM_TIME_RANGE}; +use crate::dashboard::{Panel, PanelType, Row, Unit}; fn get_storage_append_thin_state_diff_latency() -> Panel { - Panel::new( + Panel::from_hist( + &STORAGE_APPEND_THIN_STATE_DIFF_LATENCY, "Append Thin State Diff Latency", "Latency to append thin state diff in storage", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - STORAGE_APPEND_THIN_STATE_DIFF_LATENCY.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } fn get_storage_commit_latency() -> Panel { - Panel::new( + Panel::from_hist( + &STORAGE_COMMIT_LATENCY, "Storage Commit Latency", "Latency to commit changes in storage", - HISTOGRAM_QUANTILES - .iter() - .map(|q| { - format!( - "histogram_quantile({q:.2}, sum by (le) (rate({}[{HISTOGRAM_TIME_RANGE}])))", - STORAGE_COMMIT_LATENCY.get_name_with_filter(), - ) - }) - .collect(), - PanelType::TimeSeries, ) .with_unit(Unit::Seconds) } diff --git a/crates/apollo_dashboard/src/panels/tokio.rs b/crates/apollo_dashboard/src/panels/tokio.rs index 9842434a164..87094d84358 100644 --- a/crates/apollo_dashboard/src/panels/tokio.rs +++ b/crates/apollo_dashboard/src/panels/tokio.rs @@ -11,30 +11,40 @@ use apollo_monitoring_endpoint::tokio_metrics::{ use crate::dashboard::{Panel, PanelType, Row}; +const TOKIO_PANEL_LEGENDS: &[&str] = &["{{pod}}"]; + fn get_panel_tokio_total_busy_duration_micros() -> Panel { Panel::from_counter(&TOKIO_TOTAL_BUSY_DURATION_MICROS, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_min_busy_duration_micros() -> Panel { Panel::from_counter(&TOKIO_MIN_BUSY_DURATION_MICROS, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_max_busy_duration_micros() -> Panel { Panel::from_counter(&TOKIO_MAX_BUSY_DURATION_MICROS, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_total_park_count() -> Panel { Panel::from_gauge(&TOKIO_TOTAL_PARK_COUNT, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_min_park_count() -> Panel { Panel::from_gauge(&TOKIO_MIN_PARK_COUNT, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_max_park_count() -> Panel { Panel::from_gauge(&TOKIO_MAX_PARK_COUNT, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_global_queue_depth() -> Panel { Panel::from_gauge(&TOKIO_GLOBAL_QUEUE_DEPTH, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } fn get_panel_tokio_workers_count() -> Panel { Panel::from_gauge(&TOKIO_WORKERS_COUNT, PanelType::TimeSeries) + .with_legends(TOKIO_PANEL_LEGENDS.to_vec()) } pub(crate) fn get_tokio_row() -> Row { diff --git a/crates/apollo_dashboard/src/query_builder.rs b/crates/apollo_dashboard/src/query_builder.rs new file mode 100644 index 00000000000..e7a37a60f92 --- /dev/null +++ b/crates/apollo_dashboard/src/query_builder.rs @@ -0,0 +1,51 @@ +use apollo_metrics::metrics::MetricCommon; + +#[cfg(test)] +#[path = "query_builder_test.rs"] +pub mod query_builder_test; + +pub(crate) const DEFAULT_DURATION: &str = "10m"; +// Expands to the currently selected dashboard time range +pub(crate) const RANGE_DURATION: &str = "$__range"; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum DisplayMethod<'a> { + Increase(&'a str), // duration + Raw, +} + +/// Builds `increase([])` for counters. +/// +/// - `metric`: source metric (with any label filters). +/// - `duration`: range window, e.g. `"5m"`, `"1h"`. +/// +/// Example: `increase(m, "5m")` → `increase(http_requests_total{...}[5m])` +pub(crate) fn increase(metric: &dyn MetricCommon, duration: &str) -> String { + format!("increase({}[{}])", metric.get_name_with_filter(), duration) +} + +/// Returns a query string that sums a metric **by a label**, optionally using +/// `increase()` and filtering zeros. +/// +/// - `metric`: provides the metric. +/// - `label`: label key for `sum by (...)`. +/// - `display`: `DisplayMethod::Raw` or `Increase("5m")`. +/// - `filter_zeros`: if `true`, appends ` > 0`. +/// +/// Example: +/// `sum_by_label(&m, "something", DisplayMethod::Increase("5m"), true)` +/// → `sum by (something) (increase([5m])) > 0` +pub(crate) fn sum_by_label( + metric: &dyn MetricCommon, + label: &str, + display: DisplayMethod<'_>, + filter_zeros: bool, +) -> String { + let inner = match display { + DisplayMethod::Increase(duration) => increase(metric, duration), + DisplayMethod::Raw => metric.get_name_with_filter(), + }; + let filter = if filter_zeros { " > 0" } else { "" }; + + format!("sum by ({}) ({}){}", label, inner, filter) +} diff --git a/crates/apollo_dashboard/src/query_builder_test.rs b/crates/apollo_dashboard/src/query_builder_test.rs new file mode 100644 index 00000000000..fdf48d62a0d --- /dev/null +++ b/crates/apollo_dashboard/src/query_builder_test.rs @@ -0,0 +1,35 @@ +use apollo_infra_utils::template::Template; +use apollo_metrics::metric_label_filter; +use apollo_metrics::metrics::{MetricGauge, MetricScope}; +use rstest::rstest; + +use crate::query_builder::{increase, sum_by_label, DisplayMethod}; + +#[test] +fn increase_formats_correctly() { + let m = MetricGauge::new(MetricScope::Batcher, "testing", "Fake description"); + let q = increase(&m, "5m"); + let expected = + Template::new("increase({}{}[{}])").format(&[&"testing", &metric_label_filter!(), &"5m"]); + assert_eq!(q, expected); +} + +#[rstest] +#[case::raw_filtered(DisplayMethod::Raw, true)] +#[case::increase_filtered(DisplayMethod::Increase("5m"), true)] +#[case::raw_unfiltered(DisplayMethod::Raw, false)] +#[case::increase_unfiltered(DisplayMethod::Increase("15h"), false)] +fn sum_by_label_formats_correctly(#[case] display: DisplayMethod<'_>, #[case] filter_zeros: bool) { + let m = MetricGauge::new(MetricScope::Batcher, "testing", "Fake description"); + let inner = match display { + DisplayMethod::Increase(duration) => increase(&m, duration), + DisplayMethod::Raw => Template::new("{}{}").format(&[&"testing", &metric_label_filter!()]), + }; + let filter = match filter_zeros { + true => " > 0", + false => "", + }; + let q = sum_by_label(&m, "label1", display, filter_zeros); + let expected = Template::new("sum by ({}) ({}){}").format(&[&"label1", &inner, &filter]); + assert_eq!(q, expected); +} diff --git a/crates/apollo_deployments/Cargo.toml b/crates/apollo_deployments/Cargo.toml index df09365e149..7a53f9ad3aa 100644 --- a/crates/apollo_deployments/Cargo.toml +++ b/crates/apollo_deployments/Cargo.toml @@ -12,10 +12,11 @@ workspace = true alloy.workspace = true apollo_config.workspace = true apollo_http_server_config.workspace = true +apollo_infra.workspace = true apollo_infra_utils.workspace = true apollo_monitoring_endpoint_config.workspace = true -apollo_network.workspace = true apollo_node_config.workspace = true +apollo_rpc.workspace = true indexmap.workspace = true libp2p.workspace = true serde.workspace = true diff --git a/crates/apollo_deployments/resources/app_configs/batcher_config.json b/crates/apollo_deployments/resources/app_configs/batcher_config.json index a0137f4c8ca..1155cab4afe 100644 --- a/crates/apollo_deployments/resources/app_configs/batcher_config.json +++ b/crates/apollo_deployments/resources/app_configs/batcher_config.json @@ -3,7 +3,7 @@ "batcher_config.block_builder_config.bouncer_config.block_max_capacity.message_segment_length": 3700, "batcher_config.block_builder_config.bouncer_config.block_max_capacity.n_events": 5000, "batcher_config.block_builder_config.bouncer_config.block_max_capacity.n_txs": 500, - "batcher_config.block_builder_config.bouncer_config.block_max_capacity.sierra_gas": 5000000000, + "batcher_config.block_builder_config.bouncer_config.block_max_capacity.sierra_gas": 6000000000, "batcher_config.block_builder_config.bouncer_config.block_max_capacity.proving_gas": 6000000000, "batcher_config.block_builder_config.bouncer_config.block_max_capacity.state_diff_size": 4000, "batcher_config.block_builder_config.bouncer_config.builtin_weights.gas_costs.pedersen": 5722, @@ -21,6 +21,7 @@ "batcher_config.block_builder_config.execute_config.stack_size": 62914560, "batcher_config.block_builder_config.n_concurrent_txs": 100, "batcher_config.block_builder_config.tx_polling_interval_millis": 10, + "batcher_config.block_builder_config.proposer_idle_detection_delay_millis": 2000, "batcher_config.contract_class_manager_config.cairo_native_run_config.channel_size": 2000, "batcher_config.contract_class_manager_config.cairo_native_run_config.native_classes_whitelist": "All", "batcher_config.contract_class_manager_config.cairo_native_run_config.panic_on_compilation_failure": false, @@ -50,5 +51,6 @@ "batcher_config.storage.mmap_file_config.growth_step": 2147483648, "batcher_config.storage.mmap_file_config.max_object_size": 1073741824, "batcher_config.storage.mmap_file_config.max_size": 1099511627776, - "batcher_config.storage.scope": "StateOnly" -} \ No newline at end of file + "batcher_config.storage.scope": "StateOnly", + "batcher_config.propose_l1_txs_every": 10 +} diff --git a/crates/apollo_deployments/resources/app_configs/consensus_manager_config.json b/crates/apollo_deployments/resources/app_configs/consensus_manager_config.json index 041c2dd6c92..7beefb75e59 100644 --- a/crates/apollo_deployments/resources/app_configs/consensus_manager_config.json +++ b/crates/apollo_deployments/resources/app_configs/consensus_manager_config.json @@ -9,14 +9,21 @@ "consensus_manager_config.consensus_manager_config.static_config.future_height_round_limit": 5, "consensus_manager_config.consensus_manager_config.static_config.future_round_limit": 20, "consensus_manager_config.consensus_manager_config.static_config.startup_delay": 15, - "consensus_manager_config.consensus_manager_config.static_config.sync_retry_interval": 1.0, - "consensus_manager_config.consensus_manager_config.static_config.timeouts.precommit_timeout": 1.0, - "consensus_manager_config.consensus_manager_config.static_config.timeouts.prevote_timeout": 0.3, - "consensus_manager_config.consensus_manager_config.static_config.timeouts.proposal_timeout": 6.1, + "consensus_manager_config.consensus_manager_config.dynamic_config.sync_retry_interval": 1.0, + "consensus_manager_config.consensus_manager_config.dynamic_config.timeouts.precommit_timeout": 1.0, + "consensus_manager_config.consensus_manager_config.dynamic_config.timeouts.prevote_timeout": 0.3, + "consensus_manager_config.consensus_manager_config.dynamic_config.timeouts.proposal_timeout": 9.1, "consensus_manager_config.context_config.block_timestamp_window_seconds": 1, "consensus_manager_config.context_config.build_proposal_margin_millis": 1000, "consensus_manager_config.context_config.builder_address": "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", - "consensus_manager_config.context_config.constant_l2_gas_price": false, + "consensus_manager_config.context_config.override_eth_to_fri_rate": 0, + "consensus_manager_config.context_config.override_eth_to_fri_rate.#is_none": true, + "consensus_manager_config.context_config.override_l1_data_gas_price_wei": 0, + "consensus_manager_config.context_config.override_l1_data_gas_price_wei.#is_none": true, + "consensus_manager_config.context_config.override_l1_gas_price_wei": 0, + "consensus_manager_config.context_config.override_l1_gas_price_wei.#is_none": true, + "consensus_manager_config.context_config.override_l2_gas_price_fri": 0, + "consensus_manager_config.context_config.override_l2_gas_price_fri.#is_none": true, "consensus_manager_config.context_config.l1_da_mode": true, "consensus_manager_config.context_config.l1_data_gas_price_multiplier_ppt": 135, "consensus_manager_config.context_config.l1_gas_tip_wei": 1000000000, @@ -26,6 +33,7 @@ "consensus_manager_config.context_config.max_l1_gas_price_wei": 1000000000000, "consensus_manager_config.context_config.min_l1_data_gas_price_wei": 1, "consensus_manager_config.context_config.max_l1_data_gas_price_wei": 1000000000000, + "consensus_manager_config.context_config.validator_ids.#is_none": true, "consensus_manager_config.immediate_active_height": 1, "consensus_manager_config.assume_no_malicious_validators": true, "consensus_manager_config.network_config.broadcasted_message_metadata_buffer_size": 100000, @@ -36,7 +44,6 @@ "consensus_manager_config.network_config.idle_connection_timeout": 120, "consensus_manager_config.network_config.peer_manager_config.malicious_timeout_seconds": 0, "consensus_manager_config.network_config.peer_manager_config.unstable_timeout_millis": 0, - "consensus_manager_config.network_config.port": 53080, "consensus_manager_config.network_config.reported_peer_ids_buffer_size": 100000, "consensus_manager_config.network_config.session_timeout": 120, "consensus_manager_config.proposals_topic": "consensus_proposals", diff --git a/crates/apollo_deployments/resources/app_configs/gateway_config.json b/crates/apollo_deployments/resources/app_configs/gateway_config.json index 04c0a0aca28..ad761e16315 100644 --- a/crates/apollo_deployments/resources/app_configs/gateway_config.json +++ b/crates/apollo_deployments/resources/app_configs/gateway_config.json @@ -9,6 +9,7 @@ "gateway_config.stateless_tx_validator_config.max_calldata_length": 5000, "gateway_config.stateless_tx_validator_config.max_contract_bytecode_size": 81920, "gateway_config.stateless_tx_validator_config.max_contract_class_object_size": 4089446, + "gateway_config.stateless_tx_validator_config.max_l2_gas_amount": 1200000000, "gateway_config.stateless_tx_validator_config.max_sierra_version.major": 1, "gateway_config.stateless_tx_validator_config.max_sierra_version.minor": 7, "gateway_config.stateless_tx_validator_config.max_sierra_version.patch": 0, diff --git a/crates/apollo_deployments/resources/app_configs/http_server_config.json b/crates/apollo_deployments/resources/app_configs/http_server_config.json index b413553c006..c2a18ec0a34 100644 --- a/crates/apollo_deployments/resources/app_configs/http_server_config.json +++ b/crates/apollo_deployments/resources/app_configs/http_server_config.json @@ -1,4 +1,3 @@ { - "http_server_config.ip": "0.0.0.0", - "http_server_config.port": 8080 + "http_server_config.ip": "0.0.0.0" } diff --git a/crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json b/crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json index ff7b82d276a..a1ea26f6f17 100644 --- a/crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json +++ b/crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json @@ -9,7 +9,6 @@ "mempool_p2p_config.network_config.idle_connection_timeout": 120, "mempool_p2p_config.network_config.peer_manager_config.malicious_timeout_seconds": 0, "mempool_p2p_config.network_config.peer_manager_config.unstable_timeout_millis": 0, - "mempool_p2p_config.network_config.port": 53200, "mempool_p2p_config.network_config.reported_peer_ids_buffer_size": 100000, "mempool_p2p_config.network_config.session_timeout": 120, "mempool_p2p_config.transaction_batch_rate_millis": 100 diff --git a/crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json b/crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json index 9ce6e598579..7bd5f0bb9fa 100644 --- a/crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json +++ b/crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json @@ -2,7 +2,6 @@ "monitoring_endpoint_config.collect_metrics": true, "monitoring_endpoint_config.collect_profiling_metrics": true, "monitoring_endpoint_config.ip": "0.0.0.0", - "monitoring_endpoint_config.port": 8082, "monitoring_config.collect_metrics": true, "monitoring_config.collect_profiling_metrics": true } diff --git a/crates/apollo_deployments/resources/app_configs/state_sync_config.json b/crates/apollo_deployments/resources/app_configs/state_sync_config.json index 30aeb1992e2..2ccc480d2ab 100644 --- a/crates/apollo_deployments/resources/app_configs/state_sync_config.json +++ b/crates/apollo_deployments/resources/app_configs/state_sync_config.json @@ -45,7 +45,6 @@ "state_sync_config.rpc_config.max_events_chunk_size": 1000, "state_sync_config.rpc_config.max_events_keys": 100, "state_sync_config.rpc_config.ip": "0.0.0.0", - "state_sync_config.rpc_config.port": 8090, "state_sync_config.should_replay_processed_txs_metric": false, "state_sync_config.storage_config.db_config.enforce_file_exists": false, "state_sync_config.storage_config.db_config.growth_step": 67108864, diff --git a/crates/apollo_deployments/resources/deployment_inputs/mainnet.json b/crates/apollo_deployments/resources/deployment_inputs/mainnet.json index 69cbbf56e2e..7342f3e5936 100644 --- a/crates/apollo_deployments/resources/deployment_inputs/mainnet.json +++ b/crates/apollo_deployments/resources/deployment_inputs/mainnet.json @@ -1,5 +1,5 @@ { - "node_and_validator_ids": [[0, "0x1"], [1,"0x64"], [2,"0x65"], [3,"0x66"], [10,"0x1"], [11,"0x1"], [12,"0x1"], [13,"0x1"]], + "node_and_validator_ids": [[0, "0x1"], [1,"0x64"], [2,"0x65"], [3,"0x66"], [10,"0x1"], [11,"0x1"], [12,"0x1"], [13,"0x1"], [14,"0x1"], [15,"0x1"]], "num_validators": 3, "http_server_ingress_alternative_name": "alpha-mainnet.starknet.io", "ingress_domain": "starknet.io", @@ -14,5 +14,11 @@ "state_sync_type": "Central", "p2p_communication_type": "External", "deployment_environment": "Mainnet", - "requires_k8s_service_config_params": true + "audited_libfuncs_only": true, + "requires_k8s_service_config_params": true, + "http_server_port": 8080, + "monitoring_endpoint_config_port": 8082, + "state_sync_config_rpc_config_port": 8090, + "mempool_p2p_config_network_config_port": 53200, + "consensus_manager_config_network_config_port": 53080 } diff --git a/crates/apollo_deployments/resources/deployment_inputs/potc2_sepolia.json b/crates/apollo_deployments/resources/deployment_inputs/potc2_sepolia.json deleted file mode 100644 index 2aa5f47e353..00000000000 --- a/crates/apollo_deployments/resources/deployment_inputs/potc2_sepolia.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "node_and_validator_ids": [[0, "0x64"], [1,"0x65"], [2,"0x66"]], - "num_validators": 3, - "http_server_ingress_alternative_name": "potc-mock-sepolia.starknet.io", - "ingress_domain": "starknet.io", - "secret_name_format": "apollo-potc-2-sepolia-mock-sharp-{}", - "node_namespace_format": "apollo-potc-2-sepolia-mock-sharp-{}", - "starknet_contract_address": "0xd8A5518cf4AC3ECD3b4cec772478109679a73E78", - "chain_id_string": "PRIVATE_SN_POTC_MOCK_SEPOLIA", - "eth_fee_token_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "starknet_gateway_url": "https://feeder.potc-mock-sepolia-fgw.starknet.io/", - "strk_fee_token_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "l1_startup_height_override": null, - "state_sync_type": "Central", - "p2p_communication_type": "Internal", - "deployment_environment": "Potc2", - "requires_k8s_service_config_params": true -} diff --git a/crates/apollo_deployments/resources/deployment_inputs/potc_mock.json b/crates/apollo_deployments/resources/deployment_inputs/potc_mock.json new file mode 100644 index 00000000000..aec2e25963e --- /dev/null +++ b/crates/apollo_deployments/resources/deployment_inputs/potc_mock.json @@ -0,0 +1,49 @@ +{ + "node_and_validator_ids": [ + [ + 0, + "0x64" + ], + [ + 1, + "0x65" + ], + [ + 2, + "0x66" + ], + [ + 10, + "0x1" + ], + [ + 11, + "0x1" + ], + [ + 12, + "0x1" + ] + ], + "num_validators": 3, + "http_server_ingress_alternative_name": "potc-testnet-mock-sepolia.starknet.io", + "ingress_domain": "starknet.io", + "secret_name_format": "apollo-potc-mock-{}", + "node_namespace_format": "apollo-potc-mock-{}", + "starknet_contract_address": "0xd8A5518cf4AC3ECD3b4cec772478109679a73E78", + "chain_id_string": "PRIVATE_SN_POTC_MOCK_SEPOLIA", + "eth_fee_token_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "starknet_gateway_url": "https://feeder.potc-testnet-mock-sepolia.starknet.io", + "strk_fee_token_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "l1_startup_height_override": null, + "state_sync_type": "Central", + "p2p_communication_type": "Internal", + "deployment_environment": "PotcMock", + "audited_libfuncs_only": true, + "requires_k8s_service_config_params": false, + "http_server_port": 8080, + "monitoring_endpoint_config_port": 8082, + "state_sync_config_rpc_config_port": 8090, + "mempool_p2p_config_network_config_port": 53200, + "consensus_manager_config_network_config_port": 53080 +} diff --git a/crates/apollo_deployments/resources/deployment_inputs/sepolia_integration.json b/crates/apollo_deployments/resources/deployment_inputs/sepolia_integration.json index 279292daccf..54379e39e8e 100644 --- a/crates/apollo_deployments/resources/deployment_inputs/sepolia_integration.json +++ b/crates/apollo_deployments/resources/deployment_inputs/sepolia_integration.json @@ -1,5 +1,5 @@ { - "node_and_validator_ids": [[0, "0x64"], [1,"0x65"], [2,"0x66"], [12,"0x1"]], + "node_and_validator_ids": [[10, "0x64"], [11,"0x65"], [12,"0x66"]], "num_validators": 3, "http_server_ingress_alternative_name": "integration-sepolia.starknet.io", "ingress_domain": "starknet.io", @@ -14,5 +14,11 @@ "state_sync_type": "Central", "p2p_communication_type": "Internal", "deployment_environment": "SepoliaIntegration", - "requires_k8s_service_config_params": false + "audited_libfuncs_only": false, + "requires_k8s_service_config_params": false, + "http_server_port": 8080, + "monitoring_endpoint_config_port": 8082, + "state_sync_config_rpc_config_port": 8090, + "mempool_p2p_config_network_config_port": 53200, + "consensus_manager_config_network_config_port": 53080 } diff --git a/crates/apollo_deployments/resources/deployment_inputs/sepolia_testnet.json b/crates/apollo_deployments/resources/deployment_inputs/sepolia_testnet.json index 25448baaa91..ea80bb95437 100644 --- a/crates/apollo_deployments/resources/deployment_inputs/sepolia_testnet.json +++ b/crates/apollo_deployments/resources/deployment_inputs/sepolia_testnet.json @@ -1,5 +1,5 @@ { - "node_and_validator_ids": [[0, "0x1"], [1,"0x64"], [2,"0x65"], [3,"0x66"]], + "node_and_validator_ids": [[0, "0x1"], [1,"0x64"], [2,"0x65"], [3,"0x66"],[10,"0x1"], [11,"0x1"], [12,"0x1"], [13,"0x1"], [14,"0x1"], [15,"0x1"]], "num_validators": 3, "http_server_ingress_alternative_name": "alpha-sepolia.starknet.io", "ingress_domain": "starknet.io", @@ -14,5 +14,11 @@ "state_sync_type": "Central", "p2p_communication_type": "External", "deployment_environment": "SepoliaTestnet", - "requires_k8s_service_config_params": true + "audited_libfuncs_only": true, + "requires_k8s_service_config_params": true, + "http_server_port": 8080, + "monitoring_endpoint_config_port": 8082, + "state_sync_config_rpc_config_port": 8090, + "mempool_p2p_config_network_config_port": 53200, + "consensus_manager_config_network_config_port": 53080 } diff --git a/crates/apollo_deployments/resources/deployment_inputs/stress_test.json b/crates/apollo_deployments/resources/deployment_inputs/stress_test.json index 221e08a36a1..ce883b655f8 100644 --- a/crates/apollo_deployments/resources/deployment_inputs/stress_test.json +++ b/crates/apollo_deployments/resources/deployment_inputs/stress_test.json @@ -14,5 +14,11 @@ "state_sync_type": "Central", "p2p_communication_type": "Internal", "deployment_environment": "StressTest", - "requires_k8s_service_config_params": false + "audited_libfuncs_only": false, + "requires_k8s_service_config_params": false, + "http_server_port": 8080, + "monitoring_endpoint_config_port": 8082, + "state_sync_config_rpc_config_port": 8090, + "mempool_p2p_config_network_config_port": 53200, + "consensus_manager_config_network_config_port": 53080 } diff --git a/crates/apollo_deployments/resources/deployment_inputs/upgrade_test.json b/crates/apollo_deployments/resources/deployment_inputs/upgrade_test.json index b8e9a5965aa..6a1937c11de 100644 --- a/crates/apollo_deployments/resources/deployment_inputs/upgrade_test.json +++ b/crates/apollo_deployments/resources/deployment_inputs/upgrade_test.json @@ -14,5 +14,11 @@ "state_sync_type": "Central", "p2p_communication_type": "External", "deployment_environment": "UpgradeTest", - "requires_k8s_service_config_params": true + "audited_libfuncs_only": false, + "requires_k8s_service_config_params": true, + "http_server_port": 8080, + "monitoring_endpoint_config_port": 8082, + "state_sync_config_rpc_config_port": 8090, + "mempool_p2p_config_network_config_port": 53200, + "consensus_manager_config_network_config_port": 53080 } diff --git a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_1.json b/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_hybrid_14.json similarity index 85% rename from crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_1.json rename to crates/apollo_deployments/resources/deployments/mainnet/deployment_config_hybrid_14.json index 25bdd8618ff..34db1953da0 100644 --- a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_1.json +++ b/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_hybrid_14.json @@ -14,20 +14,20 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/state_sync_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_1.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_14.json", "services/hybrid/core.json" ], "ingress": null, "k8s_service_config": { "type": "LoadBalancer", - "external_dns_name": "sequencer-core-service.apollo-potc-2-sepolia-mock-sharp-1.starknet.io", + "external_dns_name": "sequencer-core-service.apollo-mainnet-14.starknet.io", "internal": true }, "autoscale": false, "replicas": 1, "storage": 1000, - "toleration": "batcher-8-64", + "toleration": "apollo-core-service-c2d-56", "resources": { "requests": { "cpu": 50, @@ -39,7 +39,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-1" + "gcsm_key": "apollo-mainnet-14" }, "anti_affinity": true, "update_strategy_type": "RollingUpdate", @@ -62,14 +62,14 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/http_server_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_1.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_14.json", "services/hybrid/http_server.json" ], "ingress": { "domain": "starknet.io", "alternative_names": [ - "potc-mock-sepolia.starknet.io" + "alpha-mainnet.starknet.io" ], "internal": false, "rules": [ @@ -96,7 +96,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-1" + "gcsm_key": "apollo-mainnet-14" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -115,8 +115,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/gateway_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_1.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_14.json", "services/hybrid/gateway.json" ], "ingress": null, @@ -136,7 +136,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-1" + "gcsm_key": "apollo-mainnet-14" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -160,8 +160,8 @@ "app_configs/l1_provider_config.json", "app_configs/l1_scraper_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_1.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_14.json", "services/hybrid/l1.json" ], "ingress": null, @@ -181,7 +181,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-1" + "gcsm_key": "apollo-mainnet-14" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -203,14 +203,14 @@ "app_configs/mempool_config.json", "app_configs/mempool_p2p_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_1.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_14.json", "services/hybrid/mempool.json" ], "ingress": null, "k8s_service_config": { "type": "LoadBalancer", - "external_dns_name": "sequencer-mempool-service.apollo-potc-2-sepolia-mock-sharp-1.starknet.io", + "external_dns_name": "sequencer-mempool-service.apollo-mainnet-14.starknet.io", "internal": true }, "autoscale": false, @@ -228,7 +228,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-1" + "gcsm_key": "apollo-mainnet-14" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -247,8 +247,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/sierra_compiler_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_1.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_14.json", "services/hybrid/sierra_compiler.json" ], "ingress": null, @@ -268,7 +268,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-1" + "gcsm_key": "apollo-mainnet-14" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", diff --git a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_2.json b/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_hybrid_15.json similarity index 85% rename from crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_2.json rename to crates/apollo_deployments/resources/deployments/mainnet/deployment_config_hybrid_15.json index 70f66e00962..a6d9504c3cf 100644 --- a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_2.json +++ b/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_hybrid_15.json @@ -14,20 +14,20 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/state_sync_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_2.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_15.json", "services/hybrid/core.json" ], "ingress": null, "k8s_service_config": { "type": "LoadBalancer", - "external_dns_name": "sequencer-core-service.apollo-potc-2-sepolia-mock-sharp-2.starknet.io", + "external_dns_name": "sequencer-core-service.apollo-mainnet-15.starknet.io", "internal": true }, "autoscale": false, "replicas": 1, "storage": 1000, - "toleration": "batcher-8-64", + "toleration": "apollo-core-service-c2d-56", "resources": { "requests": { "cpu": 50, @@ -39,7 +39,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-2" + "gcsm_key": "apollo-mainnet-15" }, "anti_affinity": true, "update_strategy_type": "RollingUpdate", @@ -62,14 +62,14 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/http_server_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_2.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_15.json", "services/hybrid/http_server.json" ], "ingress": { "domain": "starknet.io", "alternative_names": [ - "potc-mock-sepolia.starknet.io" + "alpha-mainnet.starknet.io" ], "internal": false, "rules": [ @@ -96,7 +96,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-2" + "gcsm_key": "apollo-mainnet-15" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -115,8 +115,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/gateway_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_2.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_15.json", "services/hybrid/gateway.json" ], "ingress": null, @@ -136,7 +136,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-2" + "gcsm_key": "apollo-mainnet-15" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -160,8 +160,8 @@ "app_configs/l1_provider_config.json", "app_configs/l1_scraper_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_2.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_15.json", "services/hybrid/l1.json" ], "ingress": null, @@ -181,7 +181,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-2" + "gcsm_key": "apollo-mainnet-15" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -203,14 +203,14 @@ "app_configs/mempool_config.json", "app_configs/mempool_p2p_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_2.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_15.json", "services/hybrid/mempool.json" ], "ingress": null, "k8s_service_config": { "type": "LoadBalancer", - "external_dns_name": "sequencer-mempool-service.apollo-potc-2-sepolia-mock-sharp-2.starknet.io", + "external_dns_name": "sequencer-mempool-service.apollo-mainnet-15.starknet.io", "internal": true }, "autoscale": false, @@ -228,7 +228,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-2" + "gcsm_key": "apollo-mainnet-15" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -247,8 +247,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/sierra_compiler_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_2.json", + "deployments/mainnet/deployment_config_override.json", + "deployments/mainnet/hybrid_15.json", "services/hybrid/sierra_compiler.json" ], "ingress": null, @@ -268,7 +268,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-2" + "gcsm_key": "apollo-mainnet-15" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", diff --git a/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_override.json index 2fef1d06018..b2a0fdbe273 100644 --- a/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_override.json +++ b/crates/apollo_deployments/resources/deployments/mainnet/deployment_config_override.json @@ -2,16 +2,22 @@ "base_layer_config.starknet_contract_address": "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4", "chain_id": "SN_MAIN", "consensus_manager_config.context_config.num_validators": 3, - "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-mainnet-0.starknet.io/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-mainnet-1.starknet.io/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-mainnet-2.starknet.io/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-mainnet-3.starknet.io/tcp/53080/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54,/dns/sequencer-core-service.apollo-mainnet-10.starknet.io/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-core-service.apollo-mainnet-11.starknet.io/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-core-service.apollo-mainnet-12.starknet.io/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9,/dns/sequencer-core-service.apollo-mainnet-13.starknet.io/tcp/53080/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5", + "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-mainnet-0.starknet.io/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-mainnet-1.starknet.io/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-mainnet-2.starknet.io/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-mainnet-3.starknet.io/tcp/53080/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54,/dns/sequencer-core-service.apollo-mainnet-10.starknet.io/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-core-service.apollo-mainnet-11.starknet.io/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-core-service.apollo-mainnet-12.starknet.io/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9,/dns/sequencer-core-service.apollo-mainnet-13.starknet.io/tcp/53080/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5,/dns/sequencer-core-service.apollo-mainnet-14.starknet.io/tcp/53080/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V,/dns/sequencer-core-service.apollo-mainnet-15.starknet.io/tcp/53080/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "consensus_manager_config.network_config.port": 53080, "eth_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "http_server_config.port": 8080, "l1_provider_config.provider_startup_height_override": 0, "l1_provider_config.provider_startup_height_override.#is_none": true, - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-mainnet-0.starknet.io/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-mainnet-1.starknet.io/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-mainnet-2.starknet.io/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-mainnet-3.starknet.io/tcp/53200/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54,/dns/sequencer-mempool-service.apollo-mainnet-10.starknet.io/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-mempool-service.apollo-mainnet-11.starknet.io/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-mempool-service.apollo-mainnet-12.starknet.io/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9,/dns/sequencer-mempool-service.apollo-mainnet-13.starknet.io/tcp/53200/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5", + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-mainnet-0.starknet.io/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-mainnet-1.starknet.io/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-mainnet-2.starknet.io/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-mainnet-3.starknet.io/tcp/53200/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54,/dns/sequencer-mempool-service.apollo-mainnet-10.starknet.io/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-mempool-service.apollo-mainnet-11.starknet.io/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-mempool-service.apollo-mainnet-12.starknet.io/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9,/dns/sequencer-mempool-service.apollo-mainnet-13.starknet.io/tcp/53200/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5,/dns/sequencer-mempool-service.apollo-mainnet-14.starknet.io/tcp/53200/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V,/dns/sequencer-mempool-service.apollo-mainnet-15.starknet.io/tcp/53200/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": true, "starknet_url": "https://feeder.alpha-mainnet.starknet.io/", "state_sync_config.central_sync_client_config.#is_none": false, "state_sync_config.network_config.#is_none": true, "state_sync_config.p2p_sync_client_config.#is_none": true, + "state_sync_config.rpc_config.port": 8090, "strk_fee_token_address": "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" } diff --git a/crates/apollo_deployments/resources/deployments/mainnet/hybrid_14.json b/crates/apollo_deployments/resources/deployments/mainnet/hybrid_14.json new file mode 100644 index 00000000000..067798e3131 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/mainnet/hybrid_14.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-mainnet-14.starknet.io/tcp/53080/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-mainnet-14.starknet.io/tcp/53200/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/mainnet/hybrid_15.json b/crates/apollo_deployments/resources/deployments/mainnet/hybrid_15.json new file mode 100644 index 00000000000..739d625ae9a --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/mainnet/hybrid_15.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-mainnet-15.starknet.io/tcp/53080/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-mainnet-15.starknet.io/tcp/53200/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/potc2/deployment_config_override.json deleted file mode 100644 index 9fef1f01c7d..00000000000 --- a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_override.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "base_layer_config.starknet_contract_address": "0xd8A5518cf4AC3ECD3b4cec772478109679a73E78", - "chain_id": "PRIVATE_SN_POTC_MOCK_SEPOLIA", - "consensus_manager_config.context_config.num_validators": 3, - "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-potc-2-sepolia-mock-sharp-0.svc.cluster.local/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-potc-2-sepolia-mock-sharp-1.svc.cluster.local/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-potc-2-sepolia-mock-sharp-2.svc.cluster.local/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp", - "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, - "eth_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "l1_provider_config.provider_startup_height_override": 0, - "l1_provider_config.provider_startup_height_override.#is_none": true, - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-potc-2-sepolia-mock-sharp-0.svc.cluster.local/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-potc-2-sepolia-mock-sharp-1.svc.cluster.local/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-potc-2-sepolia-mock-sharp-2.svc.cluster.local/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp", - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, - "starknet_url": "https://feeder.potc-mock-sepolia-fgw.starknet.io/", - "state_sync_config.central_sync_client_config.#is_none": false, - "state_sync_config.network_config.#is_none": true, - "state_sync_config.p2p_sync_client_config.#is_none": true, - "strk_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" -} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_0.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_0.json similarity index 85% rename from crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_0.json rename to crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_0.json index 7985cc1d67f..0e93b099535 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_0.json +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_0.json @@ -14,8 +14,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/state_sync_config.json", - "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_0.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_0.json", "services/hybrid/core.json" ], "ingress": null, @@ -23,7 +23,7 @@ "autoscale": false, "replicas": 1, "storage": 1000, - "toleration": "apollo-core-service", + "toleration": "batcher-8-64", "resources": { "requests": { "cpu": 2, @@ -35,7 +35,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-0" + "gcsm_key": "apollo-potc-mock-0" }, "anti_affinity": true, "update_strategy_type": "RollingUpdate", @@ -58,14 +58,14 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/http_server_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_0.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_0.json", "services/hybrid/http_server.json" ], "ingress": { "domain": "starknet.io", "alternative_names": [ - "integration-sepolia.starknet.io" + "potc-testnet-mock-sepolia.starknet.io" ], "internal": false, "rules": [ @@ -92,7 +92,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-0" + "gcsm_key": "apollo-potc-mock-0" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -111,8 +111,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/gateway_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_0.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_0.json", "services/hybrid/gateway.json" ], "ingress": null, @@ -132,7 +132,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-0" + "gcsm_key": "apollo-potc-mock-0" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -156,8 +156,8 @@ "app_configs/l1_provider_config.json", "app_configs/l1_scraper_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_0.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_0.json", "services/hybrid/l1.json" ], "ingress": null, @@ -177,7 +177,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-0" + "gcsm_key": "apollo-potc-mock-0" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -199,8 +199,8 @@ "app_configs/mempool_config.json", "app_configs/mempool_p2p_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_0.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_0.json", "services/hybrid/mempool.json" ], "ingress": null, @@ -220,7 +220,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-0" + "gcsm_key": "apollo-potc-mock-0" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -239,8 +239,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/sierra_compiler_config.json", - "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_0.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_0.json", "services/hybrid/sierra_compiler.json" ], "ingress": null, @@ -260,7 +260,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-0" + "gcsm_key": "apollo-potc-mock-0" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_1.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_1.json new file mode 100644 index 00000000000..4c0fec8ac4a --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_1.json @@ -0,0 +1,273 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_1.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "batcher-8-64", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 7, + "memory": 14 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-1" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_1.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "potc-testnet-mock-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-1" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_1.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-1" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_1.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-1" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_1.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-1" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_1.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-1" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_10.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_10.json new file mode 100644 index 00000000000..c309c7d1805 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_10.json @@ -0,0 +1,273 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_10.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "batcher-8-64", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 7, + "memory": 14 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-10" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_10.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "potc-testnet-mock-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-10" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_10.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-10" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_10.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-10" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_10.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-10" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_10.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-10" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_11.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_11.json new file mode 100644 index 00000000000..50e2bcb54e9 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_11.json @@ -0,0 +1,273 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_11.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "batcher-8-64", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 7, + "memory": 14 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-11" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_11.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "potc-testnet-mock-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-11" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_11.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-11" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_11.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-11" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_11.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-11" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_11.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-11" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_12.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_12.json new file mode 100644 index 00000000000..41396c2e316 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_12.json @@ -0,0 +1,273 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_12.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "batcher-8-64", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 7, + "memory": 14 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-12" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_12.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "potc-testnet-mock-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-12" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_12.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-12" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_12.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-12" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_12.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-12" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_12.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-12" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_2.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_2.json new file mode 100644 index 00000000000..6668c4af3ca --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_hybrid_2.json @@ -0,0 +1,273 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_2.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "batcher-8-64", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 7, + "memory": 14 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-2" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_2.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "potc-testnet-mock-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-2" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_2.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-2" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_2.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-2" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_2.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-2" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/potc_mock/deployment_config_override.json", + "deployments/potc_mock/hybrid_2.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-potc-mock-2" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_override.json new file mode 100644 index 00000000000..b81c7446909 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/deployment_config_override.json @@ -0,0 +1,23 @@ +{ + "base_layer_config.starknet_contract_address": "0xd8A5518cf4AC3ECD3b4cec772478109679a73E78", + "chain_id": "PRIVATE_SN_POTC_MOCK_SEPOLIA", + "consensus_manager_config.context_config.num_validators": 3, + "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-potc-mock-0.svc.cluster.local/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-potc-mock-1.svc.cluster.local/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-potc-mock-2.svc.cluster.local/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-potc-mock-10.svc.cluster.local/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-core-service.apollo-potc-mock-11.svc.cluster.local/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-core-service.apollo-potc-mock-12.svc.cluster.local/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", + "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "consensus_manager_config.network_config.port": 53080, + "eth_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "http_server_config.port": 8080, + "l1_provider_config.provider_startup_height_override": 0, + "l1_provider_config.provider_startup_height_override.#is_none": true, + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-potc-mock-0.svc.cluster.local/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-potc-mock-1.svc.cluster.local/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-potc-mock-2.svc.cluster.local/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-potc-mock-10.svc.cluster.local/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-mempool-service.apollo-potc-mock-11.svc.cluster.local/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-mempool-service.apollo-potc-mock-12.svc.cluster.local/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": true, + "starknet_url": "https://feeder.potc-testnet-mock-sepolia.starknet.io/", + "state_sync_config.central_sync_client_config.#is_none": false, + "state_sync_config.network_config.#is_none": true, + "state_sync_config.p2p_sync_client_config.#is_none": true, + "state_sync_config.rpc_config.port": 8090, + "strk_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" +} diff --git a/crates/apollo_deployments/resources/deployments/potc2/hybrid_0.json b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_0.json similarity index 100% rename from crates/apollo_deployments/resources/deployments/potc2/hybrid_0.json rename to crates/apollo_deployments/resources/deployments/potc_mock/hybrid_0.json diff --git a/crates/apollo_deployments/resources/deployments/potc2/hybrid_1.json b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_1.json similarity index 100% rename from crates/apollo_deployments/resources/deployments/potc2/hybrid_1.json rename to crates/apollo_deployments/resources/deployments/potc_mock/hybrid_1.json diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_2.json b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_10.json similarity index 92% rename from crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_2.json rename to crates/apollo_deployments/resources/deployments/potc_mock/hybrid_10.json index 25524322d64..a1e64457f84 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_2.json +++ b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_10.json @@ -3,5 +3,5 @@ "consensus_manager_config.network_config.advertised_multiaddr.#is_none": true, "mempool_p2p_config.network_config.advertised_multiaddr": "", "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": true, - "validator_id": "0x66" + "validator_id": "0x1" } diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_11.json b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_11.json new file mode 100644 index 00000000000..a1e64457f84 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_11.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": true, + "mempool_p2p_config.network_config.advertised_multiaddr": "", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": true, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_12.json b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_12.json new file mode 100644 index 00000000000..a1e64457f84 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_12.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": true, + "mempool_p2p_config.network_config.advertised_multiaddr": "", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": true, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/potc2/hybrid_2.json b/crates/apollo_deployments/resources/deployments/potc_mock/hybrid_2.json similarity index 100% rename from crates/apollo_deployments/resources/deployments/potc2/hybrid_2.json rename to crates/apollo_deployments/resources/deployments/potc_mock/hybrid_2.json diff --git a/crates/apollo_deployments/resources/deployments/replacer_deployment.json b/crates/apollo_deployments/resources/deployments/replacer_deployment.json new file mode 100644 index 00000000000..218e8a6d658 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/replacer_deployment.json @@ -0,0 +1,23 @@ +{ + "base_layer_config.starknet_contract_address": "$$$_BASE_LAYER_CONFIG-STARKNET_CONTRACT_ADDRESS_$$$", + "chain_id": "$$$_CHAIN_ID_$$$", + "consensus_manager_config.context_config.num_validators": "$$$_CONSENSUS_MANAGER_CONFIG-CONTEXT_CONFIG-NUM_VALIDATORS_$$$", + "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "$$$_CONSENSUS_MANAGER_CONFIG-NETWORK_CONFIG-BOOTSTRAP_PEER_MULTIADDR_$$$", + "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": "$$$_CONSENSUS_MANAGER_CONFIG-NETWORK_CONFIG-BOOTSTRAP_PEER_MULTIADDR-IS_NONE_$$$", + "consensus_manager_config.network_config.port": "$$$_CONSENSUS_MANAGER_CONFIG-NETWORK_CONFIG-PORT_$$$", + "eth_fee_token_address": "$$$_ETH_FEE_TOKEN_ADDRESS_$$$", + "http_server_config.port": "$$$_HTTP_SERVER_CONFIG-PORT_$$$", + "l1_provider_config.provider_startup_height_override": "$$$_L1_PROVIDER_CONFIG-PROVIDER_STARTUP_HEIGHT_OVERRIDE_$$$", + "l1_provider_config.provider_startup_height_override.#is_none": "$$$_L1_PROVIDER_CONFIG-PROVIDER_STARTUP_HEIGHT_OVERRIDE-IS_NONE_$$$", + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "$$$_MEMPOOL_P2P_CONFIG-NETWORK_CONFIG-BOOTSTRAP_PEER_MULTIADDR_$$$", + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": "$$$_MEMPOOL_P2P_CONFIG-NETWORK_CONFIG-BOOTSTRAP_PEER_MULTIADDR-IS_NONE_$$$", + "mempool_p2p_config.network_config.port": "$$$_MEMPOOL_P2P_CONFIG-NETWORK_CONFIG-PORT_$$$", + "monitoring_endpoint_config.port": "$$$_MONITORING_ENDPOINT_CONFIG-PORT_$$$", + "sierra_compiler_config.audited_libfuncs_only": "$$$_SIERRA_COMPILER_CONFIG-AUDITED_LIBFUNCS_ONLY_$$$", + "starknet_url": "$$$_STARKNET_URL_$$$", + "state_sync_config.central_sync_client_config.#is_none": "$$$_STATE_SYNC_CONFIG-CENTRAL_SYNC_CLIENT_CONFIG-IS_NONE_$$$", + "state_sync_config.network_config.#is_none": "$$$_STATE_SYNC_CONFIG-NETWORK_CONFIG-IS_NONE_$$$", + "state_sync_config.p2p_sync_client_config.#is_none": "$$$_STATE_SYNC_CONFIG-P2P_SYNC_CLIENT_CONFIG-IS_NONE_$$$", + "state_sync_config.rpc_config.port": "$$$_STATE_SYNC_CONFIG-RPC_CONFIG-PORT_$$$", + "strk_fee_token_address": "$$$_STRK_FEE_TOKEN_ADDRESS_$$$" +} diff --git a/crates/apollo_deployments/resources/deployments/replacer_instance.json b/crates/apollo_deployments/resources/deployments/replacer_instance.json new file mode 100644 index 00000000000..93e4ca688d2 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/replacer_instance.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "$$$_CONSENSUS_MANAGER_CONFIG-NETWORK_CONFIG-ADVERTISED_MULTIADDR_$$$", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": "$$$_CONSENSUS_MANAGER_CONFIG-NETWORK_CONFIG-ADVERTISED_MULTIADDR-IS_NONE_$$$", + "mempool_p2p_config.network_config.advertised_multiaddr": "$$$_MEMPOOL_P2P_CONFIG-NETWORK_CONFIG-ADVERTISED_MULTIADDR_$$$", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": "$$$_MEMPOOL_P2P_CONFIG-NETWORK_CONFIG-ADVERTISED_MULTIADDR-IS_NONE_$$$", + "validator_id": "$$$_VALIDATOR_ID_$$$" +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_1.json b/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_10.json similarity index 91% rename from crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_1.json rename to crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_10.json index 07bf7eb1887..7bc37bf0c92 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_1.json +++ b/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_10.json @@ -15,7 +15,7 @@ "app_configs/monitoring_endpoint_config.json", "app_configs/state_sync_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_1.json", + "deployments/sepolia_integration/hybrid_10.json", "services/hybrid/core.json" ], "ingress": null, @@ -35,7 +35,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-1" + "gcsm_key": "apollo-sepolia-integration-10" }, "anti_affinity": true, "update_strategy_type": "RollingUpdate", @@ -59,7 +59,7 @@ "app_configs/http_server_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_1.json", + "deployments/sepolia_integration/hybrid_10.json", "services/hybrid/http_server.json" ], "ingress": { @@ -92,7 +92,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-1" + "gcsm_key": "apollo-sepolia-integration-10" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -112,7 +112,7 @@ "app_configs/gateway_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_1.json", + "deployments/sepolia_integration/hybrid_10.json", "services/hybrid/gateway.json" ], "ingress": null, @@ -132,7 +132,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-1" + "gcsm_key": "apollo-sepolia-integration-10" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -157,7 +157,7 @@ "app_configs/l1_scraper_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_1.json", + "deployments/sepolia_integration/hybrid_10.json", "services/hybrid/l1.json" ], "ingress": null, @@ -177,7 +177,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-1" + "gcsm_key": "apollo-sepolia-integration-10" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -200,7 +200,7 @@ "app_configs/mempool_p2p_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_1.json", + "deployments/sepolia_integration/hybrid_10.json", "services/hybrid/mempool.json" ], "ingress": null, @@ -220,7 +220,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-1" + "gcsm_key": "apollo-sepolia-integration-10" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -240,7 +240,7 @@ "app_configs/monitoring_endpoint_config.json", "app_configs/sierra_compiler_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_1.json", + "deployments/sepolia_integration/hybrid_10.json", "services/hybrid/sierra_compiler.json" ], "ingress": null, @@ -260,7 +260,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-1" + "gcsm_key": "apollo-sepolia-integration-10" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_2.json b/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_11.json similarity index 91% rename from crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_2.json rename to crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_11.json index 0da4aa4340e..c967cfdd92b 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_2.json +++ b/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_hybrid_11.json @@ -15,7 +15,7 @@ "app_configs/monitoring_endpoint_config.json", "app_configs/state_sync_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_2.json", + "deployments/sepolia_integration/hybrid_11.json", "services/hybrid/core.json" ], "ingress": null, @@ -35,7 +35,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-2" + "gcsm_key": "apollo-sepolia-integration-11" }, "anti_affinity": true, "update_strategy_type": "RollingUpdate", @@ -59,7 +59,7 @@ "app_configs/http_server_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_2.json", + "deployments/sepolia_integration/hybrid_11.json", "services/hybrid/http_server.json" ], "ingress": { @@ -92,7 +92,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-2" + "gcsm_key": "apollo-sepolia-integration-11" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -112,7 +112,7 @@ "app_configs/gateway_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_2.json", + "deployments/sepolia_integration/hybrid_11.json", "services/hybrid/gateway.json" ], "ingress": null, @@ -132,7 +132,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-2" + "gcsm_key": "apollo-sepolia-integration-11" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -157,7 +157,7 @@ "app_configs/l1_scraper_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_2.json", + "deployments/sepolia_integration/hybrid_11.json", "services/hybrid/l1.json" ], "ingress": null, @@ -177,7 +177,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-2" + "gcsm_key": "apollo-sepolia-integration-11" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -200,7 +200,7 @@ "app_configs/mempool_p2p_config.json", "app_configs/monitoring_endpoint_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_2.json", + "deployments/sepolia_integration/hybrid_11.json", "services/hybrid/mempool.json" ], "ingress": null, @@ -220,7 +220,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-2" + "gcsm_key": "apollo-sepolia-integration-11" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -240,7 +240,7 @@ "app_configs/monitoring_endpoint_config.json", "app_configs/sierra_compiler_config.json", "deployments/sepolia_integration/deployment_config_override.json", - "deployments/sepolia_integration/hybrid_2.json", + "deployments/sepolia_integration/hybrid_11.json", "services/hybrid/sierra_compiler.json" ], "ingress": null, @@ -260,7 +260,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-sepolia-integration-2" + "gcsm_key": "apollo-sepolia-integration-11" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_override.json index a0f3bee17bd..f30006a31e3 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_override.json +++ b/crates/apollo_deployments/resources/deployments/sepolia_integration/deployment_config_override.json @@ -2,16 +2,22 @@ "base_layer_config.starknet_contract_address": "0x4737c0c1B4D5b1A687B42610DdabEE781152359c", "chain_id": "SN_INTEGRATION_SEPOLIA", "consensus_manager_config.context_config.num_validators": 3, - "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-integration-0.svc.cluster.local/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-sepolia-integration-1.svc.cluster.local/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-sepolia-integration-2.svc.cluster.local/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-sepolia-integration-12.svc.cluster.local/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", + "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-integration-10.svc.cluster.local/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-core-service.apollo-sepolia-integration-11.svc.cluster.local/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-core-service.apollo-sepolia-integration-12.svc.cluster.local/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "consensus_manager_config.network_config.port": 53080, "eth_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "http_server_config.port": 8080, "l1_provider_config.provider_startup_height_override": 0, "l1_provider_config.provider_startup_height_override.#is_none": true, - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-integration-0.svc.cluster.local/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-sepolia-integration-1.svc.cluster.local/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-sepolia-integration-2.svc.cluster.local/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-sepolia-integration-12.svc.cluster.local/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-integration-10.svc.cluster.local/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-mempool-service.apollo-sepolia-integration-11.svc.cluster.local/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-mempool-service.apollo-sepolia-integration-12.svc.cluster.local/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": false, "starknet_url": "https://feeder.integration-sepolia.starknet.io/", "state_sync_config.central_sync_client_config.#is_none": false, "state_sync_config.network_config.#is_none": true, "state_sync_config.p2p_sync_client_config.#is_none": true, + "state_sync_config.rpc_config.port": 8090, "strk_fee_token_address": "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" } diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_0.json b/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_10.json similarity index 100% rename from crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_0.json rename to crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_10.json diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_1.json b/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_11.json similarity index 100% rename from crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_1.json rename to crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_11.json diff --git a/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_12.json b/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_12.json index a1e64457f84..25524322d64 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_12.json +++ b/crates/apollo_deployments/resources/deployments/sepolia_integration/hybrid_12.json @@ -3,5 +3,5 @@ "consensus_manager_config.network_config.advertised_multiaddr.#is_none": true, "mempool_p2p_config.network_config.advertised_multiaddr": "", "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": true, - "validator_id": "0x1" + "validator_id": "0x66" } diff --git a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_0.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_10.json similarity index 84% rename from crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_0.json rename to crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_10.json index 4f48e00d2cb..5695f2b0a02 100644 --- a/crates/apollo_deployments/resources/deployments/potc2/deployment_config_hybrid_0.json +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_10.json @@ -14,20 +14,20 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/state_sync_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_0.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_10.json", "services/hybrid/core.json" ], "ingress": null, "k8s_service_config": { "type": "LoadBalancer", - "external_dns_name": "sequencer-core-service.apollo-potc-2-sepolia-mock-sharp-0.starknet.io", + "external_dns_name": "sequencer-core-service.apollo-sepolia-alpha-10.starknet.io", "internal": true }, "autoscale": false, "replicas": 1, "storage": 1000, - "toleration": "batcher-8-64", + "toleration": "apollo-core-service-c2d-56", "resources": { "requests": { "cpu": 50, @@ -39,7 +39,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-0" + "gcsm_key": "apollo-sepolia-alpha-10" }, "anti_affinity": true, "update_strategy_type": "RollingUpdate", @@ -62,14 +62,14 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/http_server_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_0.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_10.json", "services/hybrid/http_server.json" ], "ingress": { "domain": "starknet.io", "alternative_names": [ - "potc-mock-sepolia.starknet.io" + "alpha-sepolia.starknet.io" ], "internal": false, "rules": [ @@ -96,7 +96,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-0" + "gcsm_key": "apollo-sepolia-alpha-10" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -115,8 +115,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/gateway_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_0.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_10.json", "services/hybrid/gateway.json" ], "ingress": null, @@ -136,7 +136,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-0" + "gcsm_key": "apollo-sepolia-alpha-10" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", @@ -160,8 +160,8 @@ "app_configs/l1_provider_config.json", "app_configs/l1_scraper_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_0.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_10.json", "services/hybrid/l1.json" ], "ingress": null, @@ -181,7 +181,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-0" + "gcsm_key": "apollo-sepolia-alpha-10" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -203,14 +203,14 @@ "app_configs/mempool_config.json", "app_configs/mempool_p2p_config.json", "app_configs/monitoring_endpoint_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_0.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_10.json", "services/hybrid/mempool.json" ], "ingress": null, "k8s_service_config": { "type": "LoadBalancer", - "external_dns_name": "sequencer-mempool-service.apollo-potc-2-sepolia-mock-sharp-0.starknet.io", + "external_dns_name": "sequencer-mempool-service.apollo-sepolia-alpha-10.starknet.io", "internal": true }, "autoscale": false, @@ -228,7 +228,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-0" + "gcsm_key": "apollo-sepolia-alpha-10" }, "anti_affinity": true, "update_strategy_type": "Recreate", @@ -247,8 +247,8 @@ "app_configs/validate_resource_bounds_config.json", "app_configs/monitoring_endpoint_config.json", "app_configs/sierra_compiler_config.json", - "deployments/potc2/deployment_config_override.json", - "deployments/potc2/hybrid_0.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_10.json", "services/hybrid/sierra_compiler.json" ], "ingress": null, @@ -268,7 +268,7 @@ } }, "external_secret": { - "gcsm_key": "apollo-potc-2-sepolia-mock-sharp-0" + "gcsm_key": "apollo-sepolia-alpha-10" }, "anti_affinity": false, "update_strategy_type": "RollingUpdate", diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_11.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_11.json new file mode 100644 index 00000000000..04c45d639f7 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_11.json @@ -0,0 +1,281 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_11.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-core-service.apollo-sepolia-alpha-11.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "apollo-core-service-c2d-56", + "resources": { + "requests": { + "cpu": 50, + "memory": 200 + }, + "limits": { + "cpu": 50, + "memory": 220 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-11" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_11.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "alpha-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-11" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_11.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-11" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_11.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-11" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_11.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-mempool-service.apollo-sepolia-alpha-11.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-11" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_11.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-11" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_12.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_12.json new file mode 100644 index 00000000000..40f59e2141b --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_12.json @@ -0,0 +1,281 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_12.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-core-service.apollo-sepolia-alpha-12.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "apollo-core-service-c2d-56", + "resources": { + "requests": { + "cpu": 50, + "memory": 200 + }, + "limits": { + "cpu": 50, + "memory": 220 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-12" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_12.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "alpha-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-12" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_12.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-12" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_12.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-12" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_12.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-mempool-service.apollo-sepolia-alpha-12.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-12" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_12.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-12" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_13.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_13.json new file mode 100644 index 00000000000..1d45beff533 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_13.json @@ -0,0 +1,281 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_13.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-core-service.apollo-sepolia-alpha-13.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "apollo-core-service-c2d-56", + "resources": { + "requests": { + "cpu": 50, + "memory": 200 + }, + "limits": { + "cpu": 50, + "memory": 220 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-13" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_13.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "alpha-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-13" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_13.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-13" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_13.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-13" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_13.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-mempool-service.apollo-sepolia-alpha-13.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-13" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_13.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-13" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_14.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_14.json new file mode 100644 index 00000000000..0df9d5f301a --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_14.json @@ -0,0 +1,281 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_14.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-core-service.apollo-sepolia-alpha-14.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "apollo-core-service-c2d-56", + "resources": { + "requests": { + "cpu": 50, + "memory": 200 + }, + "limits": { + "cpu": 50, + "memory": 220 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-14" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_14.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "alpha-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-14" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_14.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-14" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_14.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-14" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_14.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-mempool-service.apollo-sepolia-alpha-14.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-14" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_14.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-14" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_15.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_15.json new file mode 100644 index 00000000000..6991bcbd937 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_hybrid_15.json @@ -0,0 +1,281 @@ +{ + "application_config_subdir": "crates/apollo_deployments/resources/", + "services": [ + { + "name": "Core", + "controller": "StatefulSet", + "config_paths": [ + "app_configs/batcher_config.json", + "app_configs/class_manager_config.json", + "app_configs/config_manager_config.json", + "app_configs/consensus_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/state_sync_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_15.json", + "services/hybrid/core.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-core-service.apollo-sepolia-alpha-15.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": 1000, + "toleration": "apollo-core-service-c2d-56", + "resources": { + "requests": { + "cpu": 50, + "memory": 200 + }, + "limits": { + "cpu": 50, + "memory": 220 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-15" + }, + "anti_affinity": true, + "update_strategy_type": "RollingUpdate", + "ports": { + "Batcher": 55000, + "ClassManager": 55001, + "SignatureManager": 55008, + "StateSync": 55009, + "ConsensusP2p": 53080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "HttpServer", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/http_server_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_15.json", + "services/hybrid/http_server.json" + ], + "ingress": { + "domain": "starknet.io", + "alternative_names": [ + "alpha-sepolia.starknet.io" + ], + "internal": false, + "rules": [ + { + "path": "/gateway", + "port": 8080, + "backend": null + } + ] + }, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 4, + "memory": 8 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-15" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "HttpServer": 8080, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Gateway", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/gateway_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_15.json", + "services/hybrid/gateway.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-15" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "Gateway": 55002, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "L1", + "controller": "Deployment", + "config_paths": [ + "app_configs/base_layer_config.json", + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/l1_endpoint_monitor_config.json", + "app_configs/l1_gas_price_provider_config.json", + "app_configs/l1_gas_price_scraper_config.json", + "app_configs/l1_provider_config.json", + "app_configs/l1_scraper_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_15.json", + "services/hybrid/l1.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-l1-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-15" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "L1EndpointMonitor": 55005, + "L1GasPriceProvider": 55003, + "L1Provider": 55004, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "Mempool", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/mempool_config.json", + "app_configs/mempool_p2p_config.json", + "app_configs/monitoring_endpoint_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_15.json", + "services/hybrid/mempool.json" + ], + "ingress": null, + "k8s_service_config": { + "type": "LoadBalancer", + "external_dns_name": "sequencer-mempool-service.apollo-sepolia-alpha-15.starknet.io", + "internal": true + }, + "autoscale": false, + "replicas": 1, + "storage": null, + "toleration": "apollo-mempool-service", + "resources": { + "requests": { + "cpu": 2, + "memory": 4 + }, + "limits": { + "cpu": 3, + "memory": 12 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-15" + }, + "anti_affinity": true, + "update_strategy_type": "Recreate", + "ports": { + "Mempool": 55006, + "MonitoringEndpoint": 8082 + } + }, + { + "name": "SierraCompiler", + "controller": "Deployment", + "config_paths": [ + "app_configs/config_manager_config.json", + "app_configs/revert_config.json", + "app_configs/versioned_constants_overrides_config.json", + "app_configs/validate_resource_bounds_config.json", + "app_configs/monitoring_endpoint_config.json", + "app_configs/sierra_compiler_config.json", + "deployments/sepolia_testnet/deployment_config_override.json", + "deployments/sepolia_testnet/hybrid_15.json", + "services/hybrid/sierra_compiler.json" + ], + "ingress": null, + "k8s_service_config": null, + "autoscale": true, + "replicas": 2, + "storage": null, + "toleration": "apollo-general-service", + "resources": { + "requests": { + "cpu": 1, + "memory": 2 + }, + "limits": { + "cpu": 2, + "memory": 4 + } + }, + "external_secret": { + "gcsm_key": "apollo-sepolia-alpha-15" + }, + "anti_affinity": false, + "update_strategy_type": "RollingUpdate", + "ports": { + "SierraCompiler": 55007, + "MonitoringEndpoint": 8082 + } + } + ] +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_override.json index c8a6ba0f72c..289419d7945 100644 --- a/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_override.json +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/deployment_config_override.json @@ -2,16 +2,22 @@ "base_layer_config.starknet_contract_address": "0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057", "chain_id": "SN_SEPOLIA", "consensus_manager_config.context_config.num_validators": 3, - "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-0.starknet.io/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-sepolia-alpha-1.starknet.io/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-sepolia-alpha-2.starknet.io/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-sepolia-alpha-3.starknet.io/tcp/53080/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54", + "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-0.starknet.io/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-sepolia-alpha-1.starknet.io/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-sepolia-alpha-2.starknet.io/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-sepolia-alpha-3.starknet.io/tcp/53080/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54,/dns/sequencer-core-service.apollo-sepolia-alpha-10.starknet.io/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-core-service.apollo-sepolia-alpha-11.starknet.io/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-core-service.apollo-sepolia-alpha-12.starknet.io/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9,/dns/sequencer-core-service.apollo-sepolia-alpha-13.starknet.io/tcp/53080/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5,/dns/sequencer-core-service.apollo-sepolia-alpha-14.starknet.io/tcp/53080/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V,/dns/sequencer-core-service.apollo-sepolia-alpha-15.starknet.io/tcp/53080/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "consensus_manager_config.network_config.port": 53080, "eth_fee_token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "http_server_config.port": 8080, "l1_provider_config.provider_startup_height_override": 0, "l1_provider_config.provider_startup_height_override.#is_none": true, - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-0.starknet.io/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-sepolia-alpha-1.starknet.io/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-sepolia-alpha-2.starknet.io/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-sepolia-alpha-3.starknet.io/tcp/53200/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54", + "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-0.starknet.io/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-sepolia-alpha-1.starknet.io/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-sepolia-alpha-2.starknet.io/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-sepolia-alpha-3.starknet.io/tcp/53200/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54,/dns/sequencer-mempool-service.apollo-sepolia-alpha-10.starknet.io/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-mempool-service.apollo-sepolia-alpha-11.starknet.io/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-mempool-service.apollo-sepolia-alpha-12.starknet.io/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9,/dns/sequencer-mempool-service.apollo-sepolia-alpha-13.starknet.io/tcp/53200/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5,/dns/sequencer-mempool-service.apollo-sepolia-alpha-14.starknet.io/tcp/53200/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V,/dns/sequencer-mempool-service.apollo-sepolia-alpha-15.starknet.io/tcp/53200/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": true, "starknet_url": "https://feeder.alpha-sepolia.starknet.io/", "state_sync_config.central_sync_client_config.#is_none": false, "state_sync_config.network_config.#is_none": true, "state_sync_config.p2p_sync_client_config.#is_none": true, + "state_sync_config.rpc_config.port": 8090, "strk_fee_token_address": "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" } diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_10.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_10.json new file mode 100644 index 00000000000..2f3cb90f689 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_10.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-10.starknet.io/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-10.starknet.io/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_11.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_11.json new file mode 100644 index 00000000000..f9d593b1bb9 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_11.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-11.starknet.io/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-11.starknet.io/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_12.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_12.json new file mode 100644 index 00000000000..58ff9545cc8 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_12.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-12.starknet.io/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-12.starknet.io/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_13.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_13.json new file mode 100644 index 00000000000..6d38c96b0f5 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_13.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-13.starknet.io/tcp/53080/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-13.starknet.io/tcp/53200/p2p/12D3KooWKZu9RjwQfiRu6VxeCUqZr6fah8BeLjKErDEw1bVDL8X5", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_14.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_14.json new file mode 100644 index 00000000000..320ace7fcb5 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_14.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-14.starknet.io/tcp/53080/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-14.starknet.io/tcp/53200/p2p/12D3KooWNEQduhMrckKco8bvmPXZvXuGo5RQNdhxL7nr6rwydJ4V", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_15.json b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_15.json new file mode 100644 index 00000000000..a4f76ab5528 --- /dev/null +++ b/crates/apollo_deployments/resources/deployments/sepolia_testnet/hybrid_15.json @@ -0,0 +1,7 @@ +{ + "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.apollo-sepolia-alpha-15.starknet.io/tcp/53080/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", + "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.apollo-sepolia-alpha-15.starknet.io/tcp/53200/p2p/12D3KooWEGF9d7XwCFL9mu6co9z7tiDA4whzVHWuZqHAjXRaYTeS", + "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, + "validator_id": "0x1" +} diff --git a/crates/apollo_deployments/resources/deployments/stress_test/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/stress_test/deployment_config_override.json index 8be398a9f19..251ee3273a1 100644 --- a/crates/apollo_deployments/resources/deployments/stress_test/deployment_config_override.json +++ b/crates/apollo_deployments/resources/deployments/stress_test/deployment_config_override.json @@ -4,14 +4,20 @@ "consensus_manager_config.context_config.num_validators": 3, "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-stresstest-dev-0.svc.cluster.local/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-stresstest-dev-1.svc.cluster.local/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-stresstest-dev-2.svc.cluster.local/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp", "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "consensus_manager_config.network_config.port": 53080, "eth_fee_token_address": "0x07e813ecf3e7b3e14f07bd2f68cb4a3d12110e3c75ec5a63de3d2dacf1852904", + "http_server_config.port": 8080, "l1_provider_config.provider_startup_height_override": 0, "l1_provider_config.provider_startup_height_override.#is_none": true, "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-stresstest-dev-0.svc.cluster.local/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-stresstest-dev-1.svc.cluster.local/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-stresstest-dev-2.svc.cluster.local/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp", "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": false, "starknet_url": "http://feeder-gateway.starknet-0-14-0-stress-test-05:9713/", "state_sync_config.central_sync_client_config.#is_none": false, "state_sync_config.network_config.#is_none": true, "state_sync_config.p2p_sync_client_config.#is_none": true, + "state_sync_config.rpc_config.port": 8090, "strk_fee_token_address": "0x02208cce4221df1f35943958340abc812aa79a8f6a533bff4ee00416d3d06cd6" } diff --git a/crates/apollo_deployments/resources/deployments/testing/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/testing/deployment_config_override.json index de56a2431ca..58e66bf8918 100644 --- a/crates/apollo_deployments/resources/deployments/testing/deployment_config_override.json +++ b/crates/apollo_deployments/resources/deployments/testing/deployment_config_override.json @@ -4,14 +4,20 @@ "consensus_manager_config.context_config.num_validators": 1, "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "", "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": true, + "consensus_manager_config.network_config.port": 53080, "eth_fee_token_address": "0x1001", + "http_server_config.port": 8080, "l1_provider_config.provider_startup_height_override": 1, "l1_provider_config.provider_startup_height_override.#is_none": false, "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "", "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": true, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": false, "starknet_url": "https://integration-sepolia.starknet.io/", "state_sync_config.central_sync_client_config.#is_none": true, "state_sync_config.network_config.#is_none": false, "state_sync_config.p2p_sync_client_config.#is_none": false, + "state_sync_config.rpc_config.port": 8090, "strk_fee_token_address": "0x1002" } diff --git a/crates/apollo_deployments/resources/deployments/testing_env_3/hybrid_3.json b/crates/apollo_deployments/resources/deployments/testing_env_3/hybrid_3.json deleted file mode 100644 index e81f2d2d515..00000000000 --- a/crates/apollo_deployments/resources/deployments/testing_env_3/hybrid_3.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "consensus_manager_config.network_config.advertised_multiaddr": "/dns/sequencer-core-service.sequencer-test-3-node-3.sw-dev.io/tcp/53080/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54", - "consensus_manager_config.network_config.advertised_multiaddr.#is_none": false, - "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.sequencer-test-3-node-0.sw-dev.io/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5", - "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, - "mempool_p2p_config.network_config.advertised_multiaddr": "/dns/sequencer-mempool-service.sequencer-test-3-node-3.sw-dev.io/tcp/53200/p2p/12D3KooWFdTjV6DXVJfQFisTXadCsqGzCbEnJJWzc6mXSPwy9g54", - "mempool_p2p_config.network_config.advertised_multiaddr.#is_none": false, - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.sequencer-test-3-node-0.sw-dev.io/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5", - "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, - "validator_id": "0x67" -} diff --git a/crates/apollo_deployments/resources/deployments/upgrade_test/deployment_config_override.json b/crates/apollo_deployments/resources/deployments/upgrade_test/deployment_config_override.json index d0f239e9a6a..22979b61438 100644 --- a/crates/apollo_deployments/resources/deployments/upgrade_test/deployment_config_override.json +++ b/crates/apollo_deployments/resources/deployments/upgrade_test/deployment_config_override.json @@ -4,14 +4,20 @@ "consensus_manager_config.context_config.num_validators": 3, "consensus_manager_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-core-service.apollo-mainnet-test-0.sw-dev.io/tcp/53080/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-core-service.apollo-mainnet-test-1.sw-dev.io/tcp/53080/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-core-service.apollo-mainnet-test-2.sw-dev.io/tcp/53080/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-core-service.apollo-mainnet-test-10.sw-dev.io/tcp/53080/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-core-service.apollo-mainnet-test-11.sw-dev.io/tcp/53080/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-core-service.apollo-mainnet-test-12.sw-dev.io/tcp/53080/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", "consensus_manager_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "consensus_manager_config.network_config.port": 53080, "eth_fee_token_address": "0x04475715fa6768670bb310eab072171856c94c1a04fa78be2370513aa2a87dc4", + "http_server_config.port": 8080, "l1_provider_config.provider_startup_height_override": 0, "l1_provider_config.provider_startup_height_override.#is_none": true, "mempool_p2p_config.network_config.bootstrap_peer_multiaddr": "/dns/sequencer-mempool-service.apollo-mainnet-test-0.sw-dev.io/tcp/53200/p2p/12D3KooWK99VoVxNE7XzyBwXEzW7xhK7Gpv85r9F3V3fyKSUKPH5,/dns/sequencer-mempool-service.apollo-mainnet-test-1.sw-dev.io/tcp/53200/p2p/12D3KooWCPzcTZ4ymgyveYaFfZ4bfWsBEh2KxuxM3Rmy7MunqHwe,/dns/sequencer-mempool-service.apollo-mainnet-test-2.sw-dev.io/tcp/53200/p2p/12D3KooWT3eoCYeMPrSNnF1eQHimWFDiqPkna7FUD6XKBw8oPiMp,/dns/sequencer-mempool-service.apollo-mainnet-test-10.sw-dev.io/tcp/53200/p2p/12D3KooWHTbjQtxM3nTF85uadLTbDxX9DXjZzzwdtAogdqxGr9t5,/dns/sequencer-mempool-service.apollo-mainnet-test-11.sw-dev.io/tcp/53200/p2p/12D3KooWJ2JJbbMRt1YrERoMPfU4hwTASZkpVqbfGiyu4d3MGGhs,/dns/sequencer-mempool-service.apollo-mainnet-test-12.sw-dev.io/tcp/53200/p2p/12D3KooWLDHhUjWxXrye37UN23QMEiLEyDt9N6ZtSAsEs2guckW9", "mempool_p2p_config.network_config.bootstrap_peer_multiaddr.#is_none": false, + "mempool_p2p_config.network_config.port": 53200, + "monitoring_endpoint_config.port": 8082, + "sierra_compiler_config.audited_libfuncs_only": false, "starknet_url": "https://feeder.sn-mainnet-test-upgrade.gateway-proxy.sw-dev.io/", "state_sync_config.central_sync_client_config.#is_none": false, "state_sync_config.network_config.#is_none": true, "state_sync_config.p2p_sync_client_config.#is_none": true, + "state_sync_config.rpc_config.port": 8090, "strk_fee_token_address": "0x06cd5b5125491c4bccec3d3b8635cbd98542c1d91a134541eca6e108cf0639f6" } diff --git a/crates/apollo_deployments/resources/services/consolidated/replacer_deployment_node.json b/crates/apollo_deployments/resources/services/consolidated/replacer_deployment_node.json new file mode 100644 index 00000000000..b1ff257fbea --- /dev/null +++ b/crates/apollo_deployments/resources/services/consolidated/replacer_deployment_node.json @@ -0,0 +1,25 @@ +[ + "crates/apollo_deployments/resources/app_configs/base_layer_config.json", + "crates/apollo_deployments/resources/app_configs/batcher_config.json", + "crates/apollo_deployments/resources/app_configs/class_manager_config.json", + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/consensus_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/gateway_config.json", + "crates/apollo_deployments/resources/app_configs/http_server_config.json", + "crates/apollo_deployments/resources/app_configs/l1_endpoint_monitor_config.json", + "crates/apollo_deployments/resources/app_configs/l1_gas_price_provider_config.json", + "crates/apollo_deployments/resources/app_configs/l1_gas_price_scraper_config.json", + "crates/apollo_deployments/resources/app_configs/l1_provider_config.json", + "crates/apollo_deployments/resources/app_configs/l1_scraper_config.json", + "crates/apollo_deployments/resources/app_configs/mempool_config.json", + "crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/app_configs/sierra_compiler_config.json", + "crates/apollo_deployments/resources/app_configs/state_sync_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/consolidated/replacer_node.json" +] diff --git a/crates/apollo_deployments/resources/services/consolidated/replacer_node.json b/crates/apollo_deployments/resources/services/consolidated/replacer_node.json new file mode 100644 index 00000000000..710fcbff460 --- /dev/null +++ b/crates/apollo_deployments/resources/services/consolidated/replacer_node.json @@ -0,0 +1,156 @@ +{ + "base_layer_config.#is_none": false, + "batcher_config.#is_none": false, + "class_manager_config.#is_none": false, + "components.batcher.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": false, + "components.batcher.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.batcher.local_server_config.inbound_requests_channel_capacity": 1024, + "components.batcher.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.batcher.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": false, + "components.class_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Enabled", + "components.gateway.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": false, + "components.gateway.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.gateway.local_server_config.inbound_requests_channel_capacity": 1024, + "components.gateway.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.gateway.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Enabled", + "components.l1_endpoint_monitor.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": false, + "components.l1_endpoint_monitor.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": false, + "components.l1_gas_price_provider.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Enabled", + "components.l1_provider.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": false, + "components.l1_provider.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Enabled", + "components.mempool.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": false, + "components.mempool.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.mempool.local_server_config.inbound_requests_channel_capacity": 1024, + "components.mempool.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.mempool.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": false, + "components.mempool_p2p.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.inbound_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": false, + "components.sierra_compiler.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.inbound_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": false, + "components.signature_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": false, + "components.state_sync.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.inbound_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": false, + "gateway_config.#is_none": false, + "http_server_config.#is_none": false, + "l1_endpoint_monitor_config.#is_none": false, + "l1_gas_price_provider_config.#is_none": false, + "l1_gas_price_scraper_config.#is_none": false, + "l1_provider_config.#is_none": false, + "l1_scraper_config.#is_none": false, + "mempool_config.#is_none": false, + "mempool_p2p_config.#is_none": false, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": false, + "state_sync_config.#is_none": false +} diff --git a/crates/apollo_deployments/resources/services/distributed/batcher.json b/crates/apollo_deployments/resources/services/distributed/batcher.json index bb07e9677bf..90e9428afd1 100644 --- a/crates/apollo_deployments/resources/services/distributed/batcher.json +++ b/crates/apollo_deployments/resources/services/distributed/batcher.json @@ -72,7 +72,7 @@ "components.l1_provider.remote_client_config.idle_timeout_ms": 30000, "components.l1_provider.remote_client_config.initial_retry_delay_ms": 1, "components.l1_provider.remote_client_config.max_retry_interval_ms": 1000, - "components.l1_provider.remote_client_config.retries": 150, + "components.l1_provider.remote_client_config.retries": 0, "components.l1_provider.url": "sequencer-l1-service", "components.l1_scraper.execution_mode": "Disabled", "components.mempool.execution_mode": "Remote", diff --git a/crates/apollo_deployments/resources/services/distributed/consensus_manager.json b/crates/apollo_deployments/resources/services/distributed/consensus_manager.json index def5e308e2b..f1aa0f9a99e 100644 --- a/crates/apollo_deployments/resources/services/distributed/consensus_manager.json +++ b/crates/apollo_deployments/resources/services/distributed/consensus_manager.json @@ -66,7 +66,7 @@ "components.l1_gas_price_provider.remote_client_config.idle_timeout_ms": 30000, "components.l1_gas_price_provider.remote_client_config.initial_retry_delay_ms": 1, "components.l1_gas_price_provider.remote_client_config.max_retry_interval_ms": 1000, - "components.l1_gas_price_provider.remote_client_config.retries": 150, + "components.l1_gas_price_provider.remote_client_config.retries": 0, "components.l1_gas_price_provider.url": "sequencer-l1-service", "components.l1_gas_price_scraper.execution_mode": "Disabled", "components.l1_provider.execution_mode": "Disabled", diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_batcher.json b/crates/apollo_deployments/resources/services/distributed/replacer_batcher.json new file mode 100644 index 00000000000..f61159e66c4 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_batcher.json @@ -0,0 +1,134 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": false, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": false, + "components.batcher.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.batcher.local_server_config.inbound_requests_channel_capacity": 1024, + "components.batcher.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.batcher.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.batcher.max_concurrency": 128, + "components.batcher.port": "$$$_COMPONENTS-BATCHER-PORT_$$$", + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "$$$_COMPONENTS-BATCHER-URL_$$$", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Remote", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": "$$$_COMPONENTS-L1_PROVIDER-PORT_$$$", + "components.l1_provider.remote_client_config.#is_none": false, + "components.l1_provider.remote_client_config.attempts_per_log": 10, + "components.l1_provider.remote_client_config.idle_connections": 10, + "components.l1_provider.remote_client_config.idle_timeout_ms": 30000, + "components.l1_provider.remote_client_config.initial_retry_delay_ms": 1, + "components.l1_provider.remote_client_config.max_retry_interval_ms": 1000, + "components.l1_provider.remote_client_config.retries": 0, + "components.l1_provider.url": "$$$_COMPONENTS-L1_PROVIDER-URL_$$$", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Remote", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": "$$$_COMPONENTS-MEMPOOL-PORT_$$$", + "components.mempool.remote_client_config.#is_none": false, + "components.mempool.remote_client_config.attempts_per_log": 10, + "components.mempool.remote_client_config.idle_connections": 10, + "components.mempool.remote_client_config.idle_timeout_ms": 30000, + "components.mempool.remote_client_config.initial_retry_delay_ms": 1, + "components.mempool.remote_client_config.max_retry_interval_ms": 1000, + "components.mempool.remote_client_config.retries": 150, + "components.mempool.url": "$$$_COMPONENTS-MEMPOOL-URL_$$$", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_class_manager.json b/crates/apollo_deployments/resources/services/distributed/replacer_class_manager.json new file mode 100644 index 00000000000..2c5f42c754e --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_class_manager.json @@ -0,0 +1,122 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": false, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": false, + "components.class_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Remote", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": "$$$_COMPONENTS-SIERRA_COMPILER-PORT_$$$", + "components.sierra_compiler.remote_client_config.#is_none": false, + "components.sierra_compiler.remote_client_config.attempts_per_log": 10, + "components.sierra_compiler.remote_client_config.idle_connections": 0, + "components.sierra_compiler.remote_client_config.idle_timeout_ms": 30000, + "components.sierra_compiler.remote_client_config.initial_retry_delay_ms": 1, + "components.sierra_compiler.remote_client_config.max_retry_interval_ms": 1000, + "components.sierra_compiler.remote_client_config.retries": 150, + "components.sierra_compiler.url": "$$$_COMPONENTS-SIERRA_COMPILER-URL_$$$", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_consensus_manager.json b/crates/apollo_deployments/resources/services/distributed/replacer_consensus_manager.json new file mode 100644 index 00000000000..60ec738a967 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_consensus_manager.json @@ -0,0 +1,142 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Remote", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": "$$$_COMPONENTS-BATCHER-PORT_$$$", + "components.batcher.remote_client_config.#is_none": false, + "components.batcher.remote_client_config.attempts_per_log": 10, + "components.batcher.remote_client_config.idle_connections": 10, + "components.batcher.remote_client_config.idle_timeout_ms": 30000, + "components.batcher.remote_client_config.initial_retry_delay_ms": 1, + "components.batcher.remote_client_config.max_retry_interval_ms": 1000, + "components.batcher.remote_client_config.retries": 150, + "components.batcher.url": "$$$_COMPONENTS-BATCHER-URL_$$$", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Enabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Remote", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-PORT_$$$", + "components.l1_gas_price_provider.remote_client_config.#is_none": false, + "components.l1_gas_price_provider.remote_client_config.attempts_per_log": 10, + "components.l1_gas_price_provider.remote_client_config.idle_connections": 10, + "components.l1_gas_price_provider.remote_client_config.idle_timeout_ms": 30000, + "components.l1_gas_price_provider.remote_client_config.initial_retry_delay_ms": 1, + "components.l1_gas_price_provider.remote_client_config.max_retry_interval_ms": 1000, + "components.l1_gas_price_provider.remote_client_config.retries": 0, + "components.l1_gas_price_provider.url": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-URL_$$$", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Remote", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": "$$$_COMPONENTS-SIGNATURE_MANAGER-PORT_$$$", + "components.signature_manager.remote_client_config.#is_none": false, + "components.signature_manager.remote_client_config.attempts_per_log": 10, + "components.signature_manager.remote_client_config.idle_connections": 10, + "components.signature_manager.remote_client_config.idle_timeout_ms": 30000, + "components.signature_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.signature_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.signature_manager.remote_client_config.retries": 150, + "components.signature_manager.url": "$$$_COMPONENTS-SIGNATURE_MANAGER-URL_$$$", + "components.state_sync.execution_mode": "Remote", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": false, + "components.state_sync.remote_client_config.attempts_per_log": 10, + "components.state_sync.remote_client_config.idle_connections": 10, + "components.state_sync.remote_client_config.idle_timeout_ms": 30000, + "components.state_sync.remote_client_config.initial_retry_delay_ms": 1, + "components.state_sync.remote_client_config.max_retry_interval_ms": 1000, + "components.state_sync.remote_client_config.retries": 150, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": false, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_batcher.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_batcher.json new file mode 100644 index 00000000000..e429ed21372 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_batcher.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/batcher_config.json", + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_batcher.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_class_manager.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_class_manager.json new file mode 100644 index 00000000000..38fba9734a3 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_class_manager.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/class_manager_config.json", + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_class_manager.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_consensus_manager.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_consensus_manager.json new file mode 100644 index 00000000000..56e8e1fefbd --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_consensus_manager.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/consensus_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_consensus_manager.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_gateway.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_gateway.json new file mode 100644 index 00000000000..ac2b3d3f70f --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_gateway.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/gateway_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_gateway.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_http_server.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_http_server.json new file mode 100644 index 00000000000..43ac4aad6e3 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_http_server.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/http_server_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_http_server.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_l1.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_l1.json new file mode 100644 index 00000000000..9b693b43ee2 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_l1.json @@ -0,0 +1,15 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/l1_endpoint_monitor_config.json", + "crates/apollo_deployments/resources/app_configs/l1_gas_price_provider_config.json", + "crates/apollo_deployments/resources/app_configs/l1_gas_price_scraper_config.json", + "crates/apollo_deployments/resources/app_configs/l1_provider_config.json", + "crates/apollo_deployments/resources/app_configs/l1_scraper_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_l1.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_mempool.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_mempool.json new file mode 100644 index 00000000000..64b36499c1b --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_mempool.json @@ -0,0 +1,12 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/mempool_config.json", + "crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_mempool.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_sierra_compiler.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_sierra_compiler.json new file mode 100644 index 00000000000..9af82dc3363 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_sierra_compiler.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/app_configs/sierra_compiler_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_sierra_compiler.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_signature_manager.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_signature_manager.json new file mode 100644 index 00000000000..d3b70ad8885 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_signature_manager.json @@ -0,0 +1,10 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_signature_manager.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_deployment_state_sync.json b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_state_sync.json new file mode 100644 index 00000000000..743ef9c283f --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_deployment_state_sync.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/app_configs/state_sync_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/distributed/replacer_state_sync.json" +] diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_gateway.json b/crates/apollo_deployments/resources/services/distributed/replacer_gateway.json new file mode 100644 index 00000000000..22ae14a4d9c --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_gateway.json @@ -0,0 +1,134 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": false, + "components.gateway.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.gateway.local_server_config.inbound_requests_channel_capacity": 1024, + "components.gateway.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.gateway.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.gateway.max_concurrency": 128, + "components.gateway.port": "$$$_COMPONENTS-GATEWAY-PORT_$$$", + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "$$$_COMPONENTS-GATEWAY-URL_$$$", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Remote", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": "$$$_COMPONENTS-MEMPOOL-PORT_$$$", + "components.mempool.remote_client_config.#is_none": false, + "components.mempool.remote_client_config.attempts_per_log": 10, + "components.mempool.remote_client_config.idle_connections": 10, + "components.mempool.remote_client_config.idle_timeout_ms": 30000, + "components.mempool.remote_client_config.initial_retry_delay_ms": 1, + "components.mempool.remote_client_config.max_retry_interval_ms": 1000, + "components.mempool.remote_client_config.retries": 150, + "components.mempool.url": "$$$_COMPONENTS-MEMPOOL-URL_$$$", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Remote", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": false, + "components.state_sync.remote_client_config.attempts_per_log": 10, + "components.state_sync.remote_client_config.idle_connections": 10, + "components.state_sync.remote_client_config.idle_timeout_ms": 30000, + "components.state_sync.remote_client_config.initial_retry_delay_ms": 1, + "components.state_sync.remote_client_config.max_retry_interval_ms": 1000, + "components.state_sync.remote_client_config.retries": 150, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": false, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_http_server.json b/crates/apollo_deployments/resources/services/distributed/replacer_http_server.json new file mode 100644 index 00000000000..c54815a8863 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_http_server.json @@ -0,0 +1,118 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Remote", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": "$$$_COMPONENTS-GATEWAY-PORT_$$$", + "components.gateway.remote_client_config.#is_none": false, + "components.gateway.remote_client_config.attempts_per_log": 10, + "components.gateway.remote_client_config.idle_connections": 0, + "components.gateway.remote_client_config.idle_timeout_ms": 30000, + "components.gateway.remote_client_config.initial_retry_delay_ms": 1, + "components.gateway.remote_client_config.max_retry_interval_ms": 1000, + "components.gateway.remote_client_config.retries": 150, + "components.gateway.url": "$$$_COMPONENTS-GATEWAY-URL_$$$", + "components.http_server.execution_mode": "Enabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": false, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_l1.json b/crates/apollo_deployments/resources/services/distributed/replacer_l1.json new file mode 100644 index 00000000000..ebc737faa8c --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_l1.json @@ -0,0 +1,136 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Remote", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": "$$$_COMPONENTS-BATCHER-PORT_$$$", + "components.batcher.remote_client_config.#is_none": false, + "components.batcher.remote_client_config.attempts_per_log": 10, + "components.batcher.remote_client_config.idle_connections": 10, + "components.batcher.remote_client_config.idle_timeout_ms": 30000, + "components.batcher.remote_client_config.initial_retry_delay_ms": 1, + "components.batcher.remote_client_config.max_retry_interval_ms": 1000, + "components.batcher.remote_client_config.retries": 150, + "components.batcher.url": "$$$_COMPONENTS-BATCHER-URL_$$$", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": false, + "components.l1_endpoint_monitor.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": "$$$_COMPONENTS-L1_ENDPOINT_MONITOR-PORT_$$$", + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "$$$_COMPONENTS-L1_ENDPOINT_MONITOR-URL_$$$", + "components.l1_gas_price_provider.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": false, + "components.l1_gas_price_provider.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-PORT_$$$", + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-URL_$$$", + "components.l1_gas_price_scraper.execution_mode": "Enabled", + "components.l1_provider.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": false, + "components.l1_provider.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": "$$$_COMPONENTS-L1_PROVIDER-PORT_$$$", + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "$$$_COMPONENTS-L1_PROVIDER-URL_$$$", + "components.l1_scraper.execution_mode": "Enabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Remote", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": false, + "components.state_sync.remote_client_config.attempts_per_log": 10, + "components.state_sync.remote_client_config.idle_connections": 10, + "components.state_sync.remote_client_config.idle_timeout_ms": 30000, + "components.state_sync.remote_client_config.initial_retry_delay_ms": 1, + "components.state_sync.remote_client_config.max_retry_interval_ms": 1000, + "components.state_sync.remote_client_config.retries": 150, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": false, + "l1_gas_price_provider_config.#is_none": false, + "l1_gas_price_scraper_config.#is_none": false, + "l1_provider_config.#is_none": false, + "l1_scraper_config.#is_none": false, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_mempool.json b/crates/apollo_deployments/resources/services/distributed/replacer_mempool.json new file mode 100644 index 00000000000..946261ba453 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_mempool.json @@ -0,0 +1,132 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Remote", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": "$$$_COMPONENTS-GATEWAY-PORT_$$$", + "components.gateway.remote_client_config.#is_none": false, + "components.gateway.remote_client_config.attempts_per_log": 10, + "components.gateway.remote_client_config.idle_connections": 0, + "components.gateway.remote_client_config.idle_timeout_ms": 30000, + "components.gateway.remote_client_config.initial_retry_delay_ms": 1, + "components.gateway.remote_client_config.max_retry_interval_ms": 1000, + "components.gateway.remote_client_config.retries": 150, + "components.gateway.url": "$$$_COMPONENTS-GATEWAY-URL_$$$", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": false, + "components.mempool.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.mempool.local_server_config.inbound_requests_channel_capacity": 1024, + "components.mempool.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.mempool.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.mempool.max_concurrency": 128, + "components.mempool.port": "$$$_COMPONENTS-MEMPOOL-PORT_$$$", + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "$$$_COMPONENTS-MEMPOOL-URL_$$$", + "components.mempool_p2p.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": false, + "components.mempool_p2p.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.inbound_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": false, + "mempool_p2p_config.#is_none": false, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_sierra_compiler.json b/crates/apollo_deployments/resources/services/distributed/replacer_sierra_compiler.json new file mode 100644 index 00000000000..5c5bbc5a3a0 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_sierra_compiler.json @@ -0,0 +1,116 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": false, + "components.sierra_compiler.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.inbound_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": "$$$_COMPONENTS-SIERRA_COMPILER-PORT_$$$", + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "$$$_COMPONENTS-SIERRA_COMPILER-URL_$$$", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": false, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_signature_manager.json b/crates/apollo_deployments/resources/services/distributed/replacer_signature_manager.json new file mode 100644 index 00000000000..767024cc8b6 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_signature_manager.json @@ -0,0 +1,116 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": false, + "components.signature_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": "$$$_COMPONENTS-SIGNATURE_MANAGER-PORT_$$$", + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "$$$_COMPONENTS-SIGNATURE_MANAGER-URL_$$$", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/distributed/replacer_state_sync.json b/crates/apollo_deployments/resources/services/distributed/replacer_state_sync.json new file mode 100644 index 00000000000..4cd74febf93 --- /dev/null +++ b/crates/apollo_deployments/resources/services/distributed/replacer_state_sync.json @@ -0,0 +1,122 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": false, + "components.state_sync.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.inbound_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": false +} diff --git a/crates/apollo_deployments/resources/services/hybrid/core.json b/crates/apollo_deployments/resources/services/hybrid/core.json index 0e2b393f773..0f6757e40f9 100644 --- a/crates/apollo_deployments/resources/services/hybrid/core.json +++ b/crates/apollo_deployments/resources/services/hybrid/core.json @@ -55,7 +55,7 @@ "components.l1_endpoint_monitor.remote_client_config.idle_timeout_ms": 30000, "components.l1_endpoint_monitor.remote_client_config.initial_retry_delay_ms": 1, "components.l1_endpoint_monitor.remote_client_config.max_retry_interval_ms": 1000, - "components.l1_endpoint_monitor.remote_client_config.retries": 150, + "components.l1_endpoint_monitor.remote_client_config.retries": 0, "components.l1_endpoint_monitor.url": "sequencer-l1-service", "components.l1_gas_price_provider.execution_mode": "Remote", "components.l1_gas_price_provider.ip": "0.0.0.0", @@ -68,7 +68,7 @@ "components.l1_gas_price_provider.remote_client_config.idle_timeout_ms": 30000, "components.l1_gas_price_provider.remote_client_config.initial_retry_delay_ms": 1, "components.l1_gas_price_provider.remote_client_config.max_retry_interval_ms": 1000, - "components.l1_gas_price_provider.remote_client_config.retries": 150, + "components.l1_gas_price_provider.remote_client_config.retries": 0, "components.l1_gas_price_provider.url": "sequencer-l1-service", "components.l1_gas_price_scraper.execution_mode": "Disabled", "components.l1_provider.execution_mode": "Remote", @@ -82,7 +82,7 @@ "components.l1_provider.remote_client_config.idle_timeout_ms": 30000, "components.l1_provider.remote_client_config.initial_retry_delay_ms": 1, "components.l1_provider.remote_client_config.max_retry_interval_ms": 1000, - "components.l1_provider.remote_client_config.retries": 150, + "components.l1_provider.remote_client_config.retries": 0, "components.l1_provider.url": "sequencer-l1-service", "components.l1_scraper.execution_mode": "Disabled", "components.mempool.execution_mode": "Remote", diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_core.json b/crates/apollo_deployments/resources/services/hybrid/replacer_core.json new file mode 100644 index 00000000000..ba3ccee5f89 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_core.json @@ -0,0 +1,158 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": false, + "class_manager_config.#is_none": false, + "components.batcher.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": false, + "components.batcher.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.batcher.local_server_config.inbound_requests_channel_capacity": 1024, + "components.batcher.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.batcher.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.batcher.max_concurrency": 128, + "components.batcher.port": "$$$_COMPONENTS-BATCHER-PORT_$$$", + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "$$$_COMPONENTS-BATCHER-URL_$$$", + "components.class_manager.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": false, + "components.class_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.class_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Enabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Remote", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": "$$$_COMPONENTS-L1_ENDPOINT_MONITOR-PORT_$$$", + "components.l1_endpoint_monitor.remote_client_config.#is_none": false, + "components.l1_endpoint_monitor.remote_client_config.attempts_per_log": 10, + "components.l1_endpoint_monitor.remote_client_config.idle_connections": 10, + "components.l1_endpoint_monitor.remote_client_config.idle_timeout_ms": 30000, + "components.l1_endpoint_monitor.remote_client_config.initial_retry_delay_ms": 1, + "components.l1_endpoint_monitor.remote_client_config.max_retry_interval_ms": 1000, + "components.l1_endpoint_monitor.remote_client_config.retries": 0, + "components.l1_endpoint_monitor.url": "$$$_COMPONENTS-L1_ENDPOINT_MONITOR-URL_$$$", + "components.l1_gas_price_provider.execution_mode": "Remote", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-PORT_$$$", + "components.l1_gas_price_provider.remote_client_config.#is_none": false, + "components.l1_gas_price_provider.remote_client_config.attempts_per_log": 10, + "components.l1_gas_price_provider.remote_client_config.idle_connections": 10, + "components.l1_gas_price_provider.remote_client_config.idle_timeout_ms": 30000, + "components.l1_gas_price_provider.remote_client_config.initial_retry_delay_ms": 1, + "components.l1_gas_price_provider.remote_client_config.max_retry_interval_ms": 1000, + "components.l1_gas_price_provider.remote_client_config.retries": 0, + "components.l1_gas_price_provider.url": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-URL_$$$", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Remote", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": "$$$_COMPONENTS-L1_PROVIDER-PORT_$$$", + "components.l1_provider.remote_client_config.#is_none": false, + "components.l1_provider.remote_client_config.attempts_per_log": 10, + "components.l1_provider.remote_client_config.idle_connections": 10, + "components.l1_provider.remote_client_config.idle_timeout_ms": 30000, + "components.l1_provider.remote_client_config.initial_retry_delay_ms": 1, + "components.l1_provider.remote_client_config.max_retry_interval_ms": 1000, + "components.l1_provider.remote_client_config.retries": 0, + "components.l1_provider.url": "$$$_COMPONENTS-L1_PROVIDER-URL_$$$", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Remote", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": "$$$_COMPONENTS-MEMPOOL-PORT_$$$", + "components.mempool.remote_client_config.#is_none": false, + "components.mempool.remote_client_config.attempts_per_log": 10, + "components.mempool.remote_client_config.idle_connections": 10, + "components.mempool.remote_client_config.idle_timeout_ms": 30000, + "components.mempool.remote_client_config.initial_retry_delay_ms": 1, + "components.mempool.remote_client_config.max_retry_interval_ms": 1000, + "components.mempool.remote_client_config.retries": 150, + "components.mempool.url": "$$$_COMPONENTS-MEMPOOL-URL_$$$", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Remote", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": "$$$_COMPONENTS-SIERRA_COMPILER-PORT_$$$", + "components.sierra_compiler.remote_client_config.#is_none": false, + "components.sierra_compiler.remote_client_config.attempts_per_log": 10, + "components.sierra_compiler.remote_client_config.idle_connections": 0, + "components.sierra_compiler.remote_client_config.idle_timeout_ms": 30000, + "components.sierra_compiler.remote_client_config.initial_retry_delay_ms": 1, + "components.sierra_compiler.remote_client_config.max_retry_interval_ms": 1000, + "components.sierra_compiler.remote_client_config.retries": 150, + "components.sierra_compiler.url": "$$$_COMPONENTS-SIERRA_COMPILER-URL_$$$", + "components.signature_manager.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": false, + "components.signature_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.signature_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": "$$$_COMPONENTS-SIGNATURE_MANAGER-PORT_$$$", + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "$$$_COMPONENTS-SIGNATURE_MANAGER-URL_$$$", + "components.state_sync.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": false, + "components.state_sync.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.inbound_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.state_sync.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": false, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": false +} diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_core.json b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_core.json new file mode 100644 index 00000000000..34375d524fe --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_core.json @@ -0,0 +1,14 @@ +[ + "crates/apollo_deployments/resources/app_configs/batcher_config.json", + "crates/apollo_deployments/resources/app_configs/class_manager_config.json", + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/consensus_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/app_configs/state_sync_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/hybrid/replacer_core.json" +] diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_gateway.json b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_gateway.json new file mode 100644 index 00000000000..01146ba5c5a --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_gateway.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/gateway_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/hybrid/replacer_gateway.json" +] diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_http_server.json b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_http_server.json new file mode 100644 index 00000000000..dd3e328bf50 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_http_server.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/http_server_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/hybrid/replacer_http_server.json" +] diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_l1.json b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_l1.json new file mode 100644 index 00000000000..9a21009359b --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_l1.json @@ -0,0 +1,16 @@ +[ + "crates/apollo_deployments/resources/app_configs/base_layer_config.json", + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/l1_endpoint_monitor_config.json", + "crates/apollo_deployments/resources/app_configs/l1_gas_price_provider_config.json", + "crates/apollo_deployments/resources/app_configs/l1_gas_price_scraper_config.json", + "crates/apollo_deployments/resources/app_configs/l1_provider_config.json", + "crates/apollo_deployments/resources/app_configs/l1_scraper_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/hybrid/replacer_l1.json" +] diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_mempool.json b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_mempool.json new file mode 100644 index 00000000000..23ff5cec9c3 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_mempool.json @@ -0,0 +1,12 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/mempool_config.json", + "crates/apollo_deployments/resources/app_configs/mempool_p2p_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/hybrid/replacer_mempool.json" +] diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_sierra_compiler.json b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_sierra_compiler.json new file mode 100644 index 00000000000..c981df0db48 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_deployment_sierra_compiler.json @@ -0,0 +1,11 @@ +[ + "crates/apollo_deployments/resources/app_configs/config_manager_config.json", + "crates/apollo_deployments/resources/app_configs/revert_config.json", + "crates/apollo_deployments/resources/app_configs/versioned_constants_overrides_config.json", + "crates/apollo_deployments/resources/app_configs/validate_resource_bounds_config.json", + "crates/apollo_deployments/resources/app_configs/monitoring_endpoint_config.json", + "crates/apollo_deployments/resources/app_configs/sierra_compiler_config.json", + "crates/apollo_deployments/resources/deployments/replacer_deployment.json", + "crates/apollo_deployments/resources/deployments/replacer_instance.json", + "crates/apollo_deployments/resources/services/hybrid/replacer_sierra_compiler.json" +] diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_gateway.json b/crates/apollo_deployments/resources/services/hybrid/replacer_gateway.json new file mode 100644 index 00000000000..22ae14a4d9c --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_gateway.json @@ -0,0 +1,134 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": false, + "components.gateway.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.gateway.local_server_config.inbound_requests_channel_capacity": 1024, + "components.gateway.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.gateway.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.gateway.max_concurrency": 128, + "components.gateway.port": "$$$_COMPONENTS-GATEWAY-PORT_$$$", + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "$$$_COMPONENTS-GATEWAY-URL_$$$", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Remote", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": "$$$_COMPONENTS-MEMPOOL-PORT_$$$", + "components.mempool.remote_client_config.#is_none": false, + "components.mempool.remote_client_config.attempts_per_log": 10, + "components.mempool.remote_client_config.idle_connections": 10, + "components.mempool.remote_client_config.idle_timeout_ms": 30000, + "components.mempool.remote_client_config.initial_retry_delay_ms": 1, + "components.mempool.remote_client_config.max_retry_interval_ms": 1000, + "components.mempool.remote_client_config.retries": 150, + "components.mempool.url": "$$$_COMPONENTS-MEMPOOL-URL_$$$", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Remote", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": false, + "components.state_sync.remote_client_config.attempts_per_log": 10, + "components.state_sync.remote_client_config.idle_connections": 10, + "components.state_sync.remote_client_config.idle_timeout_ms": 30000, + "components.state_sync.remote_client_config.initial_retry_delay_ms": 1, + "components.state_sync.remote_client_config.max_retry_interval_ms": 1000, + "components.state_sync.remote_client_config.retries": 150, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": false, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_http_server.json b/crates/apollo_deployments/resources/services/hybrid/replacer_http_server.json new file mode 100644 index 00000000000..c54815a8863 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_http_server.json @@ -0,0 +1,118 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Remote", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": "$$$_COMPONENTS-GATEWAY-PORT_$$$", + "components.gateway.remote_client_config.#is_none": false, + "components.gateway.remote_client_config.attempts_per_log": 10, + "components.gateway.remote_client_config.idle_connections": 0, + "components.gateway.remote_client_config.idle_timeout_ms": 30000, + "components.gateway.remote_client_config.initial_retry_delay_ms": 1, + "components.gateway.remote_client_config.max_retry_interval_ms": 1000, + "components.gateway.remote_client_config.retries": 150, + "components.gateway.url": "$$$_COMPONENTS-GATEWAY-URL_$$$", + "components.http_server.execution_mode": "Enabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": false, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_l1.json b/crates/apollo_deployments/resources/services/hybrid/replacer_l1.json new file mode 100644 index 00000000000..181227dded9 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_l1.json @@ -0,0 +1,136 @@ +{ + "base_layer_config.#is_none": false, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Remote", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": "$$$_COMPONENTS-BATCHER-PORT_$$$", + "components.batcher.remote_client_config.#is_none": false, + "components.batcher.remote_client_config.attempts_per_log": 10, + "components.batcher.remote_client_config.idle_connections": 10, + "components.batcher.remote_client_config.idle_timeout_ms": 30000, + "components.batcher.remote_client_config.initial_retry_delay_ms": 1, + "components.batcher.remote_client_config.max_retry_interval_ms": 1000, + "components.batcher.remote_client_config.retries": 150, + "components.batcher.url": "$$$_COMPONENTS-BATCHER-URL_$$$", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": false, + "components.l1_endpoint_monitor.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_endpoint_monitor.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": false, + "components.l1_gas_price_provider.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_gas_price_provider.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-PORT_$$$", + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "$$$_COMPONENTS-L1_GAS_PRICE_PROVIDER-URL_$$$", + "components.l1_gas_price_scraper.execution_mode": "Enabled", + "components.l1_provider.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": false, + "components.l1_provider.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.inbound_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.l1_provider.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": "$$$_COMPONENTS-L1_PROVIDER-PORT_$$$", + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "$$$_COMPONENTS-L1_PROVIDER-URL_$$$", + "components.l1_scraper.execution_mode": "Enabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Remote", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": "$$$_COMPONENTS-STATE_SYNC-PORT_$$$", + "components.state_sync.remote_client_config.#is_none": false, + "components.state_sync.remote_client_config.attempts_per_log": 10, + "components.state_sync.remote_client_config.idle_connections": 10, + "components.state_sync.remote_client_config.idle_timeout_ms": 30000, + "components.state_sync.remote_client_config.initial_retry_delay_ms": 1, + "components.state_sync.remote_client_config.max_retry_interval_ms": 1000, + "components.state_sync.remote_client_config.retries": 150, + "components.state_sync.url": "$$$_COMPONENTS-STATE_SYNC-URL_$$$", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": false, + "l1_gas_price_provider_config.#is_none": false, + "l1_gas_price_scraper_config.#is_none": false, + "l1_provider_config.#is_none": false, + "l1_scraper_config.#is_none": false, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_mempool.json b/crates/apollo_deployments/resources/services/hybrid/replacer_mempool.json new file mode 100644 index 00000000000..946261ba453 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_mempool.json @@ -0,0 +1,132 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Remote", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": "$$$_COMPONENTS-CLASS_MANAGER-PORT_$$$", + "components.class_manager.remote_client_config.#is_none": false, + "components.class_manager.remote_client_config.attempts_per_log": 10, + "components.class_manager.remote_client_config.idle_connections": 10, + "components.class_manager.remote_client_config.idle_timeout_ms": 30000, + "components.class_manager.remote_client_config.initial_retry_delay_ms": 1, + "components.class_manager.remote_client_config.max_retry_interval_ms": 1000, + "components.class_manager.remote_client_config.retries": 150, + "components.class_manager.url": "$$$_COMPONENTS-CLASS_MANAGER-URL_$$$", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Remote", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": "$$$_COMPONENTS-GATEWAY-PORT_$$$", + "components.gateway.remote_client_config.#is_none": false, + "components.gateway.remote_client_config.attempts_per_log": 10, + "components.gateway.remote_client_config.idle_connections": 0, + "components.gateway.remote_client_config.idle_timeout_ms": 30000, + "components.gateway.remote_client_config.initial_retry_delay_ms": 1, + "components.gateway.remote_client_config.max_retry_interval_ms": 1000, + "components.gateway.remote_client_config.retries": 150, + "components.gateway.url": "$$$_COMPONENTS-GATEWAY-URL_$$$", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": false, + "components.mempool.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.mempool.local_server_config.inbound_requests_channel_capacity": 1024, + "components.mempool.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.mempool.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.mempool.max_concurrency": 128, + "components.mempool.port": "$$$_COMPONENTS-MEMPOOL-PORT_$$$", + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "$$$_COMPONENTS-MEMPOOL-URL_$$$", + "components.mempool_p2p.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": false, + "components.mempool_p2p.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.inbound_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.mempool_p2p.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "Disabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": true, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": 0, + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "localhost", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": false, + "mempool_p2p_config.#is_none": false, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": true, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/resources/services/hybrid/replacer_sierra_compiler.json b/crates/apollo_deployments/resources/services/hybrid/replacer_sierra_compiler.json new file mode 100644 index 00000000000..5c5bbc5a3a0 --- /dev/null +++ b/crates/apollo_deployments/resources/services/hybrid/replacer_sierra_compiler.json @@ -0,0 +1,116 @@ +{ + "base_layer_config.#is_none": true, + "batcher_config.#is_none": true, + "class_manager_config.#is_none": true, + "components.batcher.execution_mode": "Disabled", + "components.batcher.ip": "0.0.0.0", + "components.batcher.local_server_config.#is_none": true, + "components.batcher.max_concurrency": 128, + "components.batcher.port": 0, + "components.batcher.remote_client_config.#is_none": true, + "components.batcher.url": "localhost", + "components.class_manager.execution_mode": "Disabled", + "components.class_manager.ip": "0.0.0.0", + "components.class_manager.local_server_config.#is_none": true, + "components.class_manager.max_concurrency": 128, + "components.class_manager.port": 0, + "components.class_manager.remote_client_config.#is_none": true, + "components.class_manager.url": "localhost", + "components.config_manager.execution_mode": "LocalExecutionWithRemoteDisabled", + "components.config_manager.ip": "0.0.0.0", + "components.config_manager.local_server_config.#is_none": false, + "components.config_manager.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.inbound_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.config_manager.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.config_manager.max_concurrency": 128, + "components.config_manager.port": 0, + "components.config_manager.remote_client_config.#is_none": true, + "components.config_manager.url": "localhost", + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.gateway.ip": "0.0.0.0", + "components.gateway.local_server_config.#is_none": true, + "components.gateway.max_concurrency": 128, + "components.gateway.port": 0, + "components.gateway.remote_client_config.#is_none": true, + "components.gateway.url": "localhost", + "components.http_server.execution_mode": "Disabled", + "components.l1_endpoint_monitor.execution_mode": "Disabled", + "components.l1_endpoint_monitor.ip": "0.0.0.0", + "components.l1_endpoint_monitor.local_server_config.#is_none": true, + "components.l1_endpoint_monitor.max_concurrency": 128, + "components.l1_endpoint_monitor.port": 0, + "components.l1_endpoint_monitor.remote_client_config.#is_none": true, + "components.l1_endpoint_monitor.url": "localhost", + "components.l1_gas_price_provider.execution_mode": "Disabled", + "components.l1_gas_price_provider.ip": "0.0.0.0", + "components.l1_gas_price_provider.local_server_config.#is_none": true, + "components.l1_gas_price_provider.max_concurrency": 128, + "components.l1_gas_price_provider.port": 0, + "components.l1_gas_price_provider.remote_client_config.#is_none": true, + "components.l1_gas_price_provider.url": "localhost", + "components.l1_gas_price_scraper.execution_mode": "Disabled", + "components.l1_provider.execution_mode": "Disabled", + "components.l1_provider.ip": "0.0.0.0", + "components.l1_provider.local_server_config.#is_none": true, + "components.l1_provider.max_concurrency": 128, + "components.l1_provider.port": 0, + "components.l1_provider.remote_client_config.#is_none": true, + "components.l1_provider.url": "localhost", + "components.l1_scraper.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool.ip": "0.0.0.0", + "components.mempool.local_server_config.#is_none": true, + "components.mempool.max_concurrency": 128, + "components.mempool.port": 0, + "components.mempool.remote_client_config.#is_none": true, + "components.mempool.url": "localhost", + "components.mempool_p2p.execution_mode": "Disabled", + "components.mempool_p2p.ip": "0.0.0.0", + "components.mempool_p2p.local_server_config.#is_none": true, + "components.mempool_p2p.max_concurrency": 128, + "components.mempool_p2p.port": 0, + "components.mempool_p2p.remote_client_config.#is_none": true, + "components.mempool_p2p.url": "localhost", + "components.monitoring_endpoint.execution_mode": "Enabled", + "components.sierra_compiler.execution_mode": "LocalExecutionWithRemoteEnabled", + "components.sierra_compiler.ip": "0.0.0.0", + "components.sierra_compiler.local_server_config.#is_none": false, + "components.sierra_compiler.local_server_config.high_priority_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.inbound_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.normal_priority_requests_channel_capacity": 1024, + "components.sierra_compiler.local_server_config.processing_time_warning_threshold_ms": 3000, + "components.sierra_compiler.max_concurrency": 128, + "components.sierra_compiler.port": "$$$_COMPONENTS-SIERRA_COMPILER-PORT_$$$", + "components.sierra_compiler.remote_client_config.#is_none": true, + "components.sierra_compiler.url": "$$$_COMPONENTS-SIERRA_COMPILER-URL_$$$", + "components.signature_manager.execution_mode": "Disabled", + "components.signature_manager.ip": "0.0.0.0", + "components.signature_manager.local_server_config.#is_none": true, + "components.signature_manager.max_concurrency": 128, + "components.signature_manager.port": 0, + "components.signature_manager.remote_client_config.#is_none": true, + "components.signature_manager.url": "localhost", + "components.state_sync.execution_mode": "Disabled", + "components.state_sync.ip": "0.0.0.0", + "components.state_sync.local_server_config.#is_none": true, + "components.state_sync.max_concurrency": 128, + "components.state_sync.port": 0, + "components.state_sync.remote_client_config.#is_none": true, + "components.state_sync.url": "localhost", + "config_manager_config.#is_none": false, + "consensus_manager_config.#is_none": true, + "gateway_config.#is_none": true, + "http_server_config.#is_none": true, + "l1_endpoint_monitor_config.#is_none": true, + "l1_gas_price_provider_config.#is_none": true, + "l1_gas_price_scraper_config.#is_none": true, + "l1_provider_config.#is_none": true, + "l1_scraper_config.#is_none": true, + "mempool_config.#is_none": true, + "mempool_p2p_config.#is_none": true, + "monitoring_endpoint_config.#is_none": false, + "sierra_compiler_config.#is_none": false, + "state_sync_config.#is_none": true +} diff --git a/crates/apollo_deployments/src/config_override.rs b/crates/apollo_deployments/src/config_override.rs index 810b4af72ed..42f3f43cda1 100644 --- a/crates/apollo_deployments/src/config_override.rs +++ b/crates/apollo_deployments/src/config_override.rs @@ -1,9 +1,9 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; +use apollo_config::converters::serialize_optional_comma_separated; use apollo_infra_utils::dumping::serialize_to_file; #[cfg(test)] use apollo_infra_utils::dumping::serialize_to_file_test; -use apollo_network::serialize_multi_addrs; use libp2p::Multiaddr; use serde::{Serialize, Serializer}; use serde_json::to_value; @@ -12,10 +12,14 @@ use starknet_api::block::BlockNumber; use url::Url; use crate::deployment_definitions::{StateSyncConfig, StateSyncType}; +use crate::replacers::insert_replacer_annotations; #[cfg(test)] use crate::test_utils::FIX_BINARY_NAME; const DEPLOYMENT_FILE_NAME: &str = "deployment_config_override.json"; +const REPLACER_DEPLOYMENT_FILE_NAME: &str = "replacer_deployment.json"; +const REPLACER_INSTANCE_FILE_NAME: &str = "replacer_instance.json"; +const REPLACER_DIR: &str = "crates/apollo_deployments/resources/deployments/"; // Serialization prefixes for p2p configs with_prefix!(consensus_prefix "consensus_manager_config.network_config."); @@ -27,6 +31,14 @@ pub struct ConfigOverride { instance_config_override: InstanceConfigOverride, } +pub(crate) fn deployment_replacer_file_path() -> String { + PathBuf::from(REPLACER_DIR).join(REPLACER_DEPLOYMENT_FILE_NAME).to_string_lossy().to_string() +} + +pub(crate) fn instance_replacer_file_path() -> String { + PathBuf::from(REPLACER_DIR).join(REPLACER_INSTANCE_FILE_NAME).to_string_lossy().to_string() +} + impl ConfigOverride { pub const fn new( deployment_config_override: DeploymentConfigOverride, @@ -45,14 +57,18 @@ impl ConfigOverride { let instance_path = deployment_config_override_dir.join(format!("{instance_name}.json")); if create { + let deployment_data = to_value(&self.deployment_config_override).unwrap(); + serialize_to_file(&deployment_data, deployment_path.to_str().unwrap()); serialize_to_file( - to_value(&self.deployment_config_override).unwrap(), - deployment_path.to_str().unwrap(), + &insert_replacer_annotations(deployment_data, |_, _| true), + &deployment_replacer_file_path(), ); + let instance_data = to_value(&self.instance_config_override).unwrap(); + serialize_to_file(&instance_data, instance_path.to_str().unwrap()); serialize_to_file( - to_value(&self.instance_config_override).unwrap(), - instance_path.to_str().unwrap(), + &insert_replacer_annotations(instance_data, |_, _| true), + &instance_replacer_file_path(), ); } @@ -96,13 +112,13 @@ impl ConfigOverride { self.config_files(deployment_config_override_dir, instance_name, false); serialize_to_file_test( - to_value(config_override_with_paths.deployment_config_override).unwrap(), + &to_value(config_override_with_paths.deployment_config_override).unwrap(), &config_override_with_paths.deployment_path, FIX_BINARY_NAME, ); serialize_to_file_test( - to_value(config_override_with_paths.instance_config_override).unwrap(), + &to_value(config_override_with_paths.instance_config_override).unwrap(), &config_override_with_paths.instance_path, FIX_BINARY_NAME, ); @@ -132,12 +148,24 @@ pub struct DeploymentConfigOverride { l1_provider_config_provider_startup_height_override_is_none: bool, #[serde(rename = "consensus_manager_config.context_config.num_validators")] consensus_manager_config_context_config_num_validators: usize, + #[serde(rename = "sierra_compiler_config.audited_libfuncs_only")] + sierra_compiler_config_audited_libfuncs_only: bool, #[serde(flatten)] state_sync_config: StateSyncConfig, #[serde(flatten, with = "consensus_prefix")] consensus_p2p_bootstrap_config: PeerToPeerBootstrapConfig, #[serde(flatten, with = "mempool_prefix")] mempool_p2p_bootstrap_config: PeerToPeerBootstrapConfig, + #[serde(rename = "http_server_config.port")] + http_server_config_port: u16, + #[serde(rename = "monitoring_endpoint_config.port")] + monitoring_endpoint_config_port: u16, + #[serde(rename = "state_sync_config.rpc_config.port")] + state_sync_config_rpc_config_port: u16, + #[serde(rename = "mempool_p2p_config.network_config.port")] + mempool_p2p_config_network_config_port: u16, + #[serde(rename = "consensus_manager_config.network_config.port")] + consensus_manager_config_network_config_port: u16, } impl DeploymentConfigOverride { @@ -153,6 +181,12 @@ impl DeploymentConfigOverride { state_sync_type: StateSyncType, consensus_p2p_bootstrap_config: PeerToPeerBootstrapConfig, mempool_p2p_bootstrap_config: PeerToPeerBootstrapConfig, + sierra_compiler_config_audited_libfuncs_only: bool, + http_server_config_port: u16, + monitoring_endpoint_config_port: u16, + state_sync_config_rpc_config_port: u16, + mempool_p2p_config_network_config_port: u16, + consensus_manager_config_network_config_port: u16, ) -> Self { let ( l1_provider_config_provider_startup_height_override, @@ -171,9 +205,15 @@ impl DeploymentConfigOverride { l1_provider_config_provider_startup_height_override, l1_provider_config_provider_startup_height_override_is_none, consensus_manager_config_context_config_num_validators, + sierra_compiler_config_audited_libfuncs_only, state_sync_config: state_sync_type.get_state_sync_config(), consensus_p2p_bootstrap_config, mempool_p2p_bootstrap_config, + http_server_config_port, + monitoring_endpoint_config_port, + state_sync_config_rpc_config_port, + mempool_p2p_config_network_config_port, + consensus_manager_config_network_config_port, } } } @@ -181,7 +221,10 @@ impl DeploymentConfigOverride { #[derive(Clone, Debug, Serialize, PartialEq)] pub struct PeerToPeerBootstrapConfig { // Bootstrap peer address. - #[serde(rename = "bootstrap_peer_multiaddr", serialize_with = "serialize_multi_addrs_wrapper")] + #[serde( + rename = "bootstrap_peer_multiaddr", + serialize_with = "serialize_optional_comma_separated_wrapper" + )] bootstrap_peers_multiaddrs: Option>, #[serde(rename = "bootstrap_peer_multiaddr.#is_none")] bootstrap_peer_multiaddr_is_none: bool, @@ -240,23 +283,19 @@ impl InstanceConfigOverride { } } -// Wrapper function for the custom `serialize_multi_addrs` function, to be +// Wrapper function for the generic `serialize_optional_comma_separated` function, to be // compatible with serde's `serialize_with` attribute. It first applies the custom serialization // logic to convert the optional list into a `String`, and then serializes that string. -fn serialize_multi_addrs_wrapper( - optional_multi_addrs: &Option>, +fn serialize_optional_comma_separated_wrapper( + optional_list: &Option>, serializer: S, ) -> Result where S: Serializer, + T: ToString, { - match optional_multi_addrs { + match serialize_optional_comma_separated(optional_list) { None => serializer.serialize_none(), - Some(multi_addrs) => { - // Call the implemented custom serialization function - let s = serialize_multi_addrs(&Some(multi_addrs.clone())); - // Serialize the returned String - serializer.serialize_some(&s) - } + Some(s) => serializer.serialize_some(&s), } } diff --git a/crates/apollo_deployments/src/deployment_definitions.rs b/crates/apollo_deployments/src/deployment_definitions.rs index e2e5ea0e7bc..600880fe953 100644 --- a/crates/apollo_deployments/src/deployment_definitions.rs +++ b/crates/apollo_deployments/src/deployment_definitions.rs @@ -40,7 +40,7 @@ const SIGNATURE_MANAGER_PORT: u16 = 55008; const STATE_SYNC_PORT: u16 = 55009; pub const DEPLOYMENTS: &[DeploymentFn] = &[ - || load_and_create_hybrid_deployments(POTC2_DEPLOYMENT_INPUTS_PATH), + || load_and_create_hybrid_deployments(POTC_MOCK_DEPLOYMENT_INPUTS_PATH), || load_and_create_hybrid_deployments(MAINNET_DEPLOYMENT_INPUTS_PATH), || load_and_create_hybrid_deployments(INTEGRATION_DEPLOYMENT_INPUTS_PATH), || load_and_create_hybrid_deployments(TESTNET_DEPLOYMENT_INPUTS_PATH), @@ -54,8 +54,8 @@ pub(crate) const DEPLOYMENT_CONFIG_DIR_NAME: &str = "deployments/"; const BASE_APP_CONFIGS_DIR_PATH: &str = "crates/apollo_deployments/resources/app_configs"; -const POTC2_DEPLOYMENT_INPUTS_PATH: &str = - "crates/apollo_deployments/resources/deployment_inputs/potc2_sepolia.json"; +const POTC_MOCK_DEPLOYMENT_INPUTS_PATH: &str = + "crates/apollo_deployments/resources/deployment_inputs/potc_mock.json"; const MAINNET_DEPLOYMENT_INPUTS_PATH: &str = "crates/apollo_deployments/resources/deployment_inputs/mainnet.json"; const INTEGRATION_DEPLOYMENT_INPUTS_PATH: &str = @@ -87,6 +87,12 @@ pub struct DeploymentInputs { pub p2p_communication_type: P2PCommunicationType, pub deployment_environment: Environment, pub requires_k8s_service_config_params: bool, + pub audited_libfuncs_only: bool, + pub http_server_port: u16, + pub monitoring_endpoint_config_port: u16, + pub state_sync_config_rpc_config_port: u16, + pub mempool_p2p_config_network_config_port: u16, + pub consensus_manager_config_network_config_port: u16, } impl DeploymentInputs { @@ -130,7 +136,7 @@ impl Display for Environment { #[derive(EnumString, Clone, Display, PartialEq, Debug, Serialize, Deserialize)] #[strum(serialize_all = "snake_case")] pub enum CloudK8sEnvironment { - Potc2, + PotcMock, Mainnet, SepoliaIntegration, SepoliaTestnet, @@ -268,7 +274,7 @@ impl ServicePort { } } -#[derive(Clone, Debug, Display, Serialize, PartialEq, Eq, PartialOrd, Ord, EnumIter)] +#[derive(Hash, Clone, Debug, Display, Serialize, PartialEq, Eq, PartialOrd, Ord, EnumIter)] pub enum ComponentConfigInService { BaseLayer, Batcher, diff --git a/crates/apollo_deployments/src/deployment_definitions/testing.rs b/crates/apollo_deployments/src/deployment_definitions/testing.rs index 5d78e8c5dcd..7270bd31e68 100644 --- a/crates/apollo_deployments/src/deployment_definitions/testing.rs +++ b/crates/apollo_deployments/src/deployment_definitions/testing.rs @@ -1,3 +1,6 @@ +use apollo_http_server_config::config::HTTP_SERVER_PORT; +use apollo_monitoring_endpoint_config::config::MONITORING_ENDPOINT_DEFAULT_PORT; +use apollo_rpc::RPC_CONFIG_DEFAULT_PORT; use starknet_api::block::BlockNumber; use url::Url; @@ -9,7 +12,12 @@ use crate::config_override::{ PeerToPeerBootstrapConfig, }; use crate::deployment::Deployment; -use crate::deployment_definitions::{Environment, StateSyncType}; +use crate::deployment_definitions::{ + Environment, + StateSyncType, + CONSENSUS_P2P_PORT, + MEMPOOL_P2P_PORT, +}; use crate::k8s::IngressParams; use crate::service::NodeType; @@ -36,6 +44,12 @@ fn testing_deployment_config_override() -> DeploymentConfigOverride { StateSyncType::P2P, PeerToPeerBootstrapConfig::new(None), PeerToPeerBootstrapConfig::new(None), + false, + HTTP_SERVER_PORT, + MONITORING_ENDPOINT_DEFAULT_PORT, + RPC_CONFIG_DEFAULT_PORT, + MEMPOOL_P2P_PORT, + CONSENSUS_P2P_PORT, ) } diff --git a/crates/apollo_deployments/src/deployment_definitions_test.rs b/crates/apollo_deployments/src/deployment_definitions_test.rs index 52cec098d8c..6ed64d4576b 100644 --- a/crates/apollo_deployments/src/deployment_definitions_test.rs +++ b/crates/apollo_deployments/src/deployment_definitions_test.rs @@ -51,7 +51,7 @@ fn load_and_process_service_config_files() { let temp_file = NamedTempFile::new().unwrap(); let temp_file_path = temp_file.path().to_str().unwrap(); let secrets_config_override = SecretsConfigOverride::default(); - serialize_to_file(to_value(&secrets_config_override).unwrap(), temp_file_path); + serialize_to_file(&to_value(&secrets_config_override).unwrap(), temp_file_path); for deployment in DEPLOYMENTS.iter().flat_map(|f| f()) { for mut service_config_paths in deployment.get_all_services_config_paths().into_iter() { diff --git a/crates/apollo_deployments/src/deployments/consolidated.rs b/crates/apollo_deployments/src/deployments/consolidated.rs index 7277717e8b3..a755fbd7218 100644 --- a/crates/apollo_deployments/src/deployments/consolidated.rs +++ b/crates/apollo_deployments/src/deployments/consolidated.rs @@ -1,5 +1,6 @@ use std::collections::BTreeSet; +use apollo_infra::component_client::remote_component_client::DEFAULT_RETRIES; use apollo_node_config::component_config::ComponentConfig; use apollo_node_config::component_execution_config::{ ActiveComponentExecutionConfig, @@ -69,6 +70,12 @@ impl ServiceNameInner for ConsolidatedNodeServiceName { } } + fn get_retries(&self) -> usize { + match self { + Self::Node => DEFAULT_RETRIES, + } + } + fn get_toleration(&self, environment: &Environment) -> Option { match environment { Environment::CloudK8s(_) => Some(Toleration::ApolloCoreService), diff --git a/crates/apollo_deployments/src/deployments/distributed.rs b/crates/apollo_deployments/src/deployments/distributed.rs index 4f25e7f104a..abdff035753 100644 --- a/crates/apollo_deployments/src/deployments/distributed.rs +++ b/crates/apollo_deployments/src/deployments/distributed.rs @@ -1,5 +1,6 @@ use std::collections::{BTreeMap, BTreeSet}; +use apollo_infra::component_client::DEFAULT_RETRIES; use apollo_node_config::component_config::ComponentConfig; use apollo_node_config::component_execution_config::{ ActiveComponentExecutionConfig, @@ -38,6 +39,8 @@ const BATCHER_STORAGE: usize = 500; const CLASS_MANAGER_STORAGE: usize = 500; const STATE_SYNC_STORAGE: usize = 500; +pub const RETRIES_FOR_L1_SERVICES: usize = 0; + // TODO(Tsabary): define consts and functions whenever relevant. #[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Hash, Serialize, AsRefStr, EnumIter)] @@ -198,6 +201,21 @@ impl ServiceNameInner for DistributedNodeServiceName { } } + fn get_retries(&self) -> usize { + match self { + Self::Batcher + | Self::ClassManager + | Self::ConsensusManager + | Self::HttpServer + | Self::Mempool + | Self::StateSync + | Self::SignatureManager + | Self::Gateway + | Self::SierraCompiler => DEFAULT_RETRIES, + Self::L1 => RETRIES_FOR_L1_SERVICES, + } + } + fn get_toleration(&self, environment: &Environment) -> Option { match environment { Environment::CloudK8s(_) => match self { diff --git a/crates/apollo_deployments/src/deployments/hybrid.rs b/crates/apollo_deployments/src/deployments/hybrid.rs index c5527e02437..f828d1290a0 100644 --- a/crates/apollo_deployments/src/deployments/hybrid.rs +++ b/crates/apollo_deployments/src/deployments/hybrid.rs @@ -1,5 +1,6 @@ use std::collections::{BTreeMap, BTreeSet}; +use apollo_infra::component_client::DEFAULT_RETRIES; use apollo_infra_utils::path::resolve_project_relative_path; use apollo_infra_utils::template::Template; use apollo_node_config::component_config::ComponentConfig; @@ -33,6 +34,7 @@ use crate::deployment_definitions::{ CONSENSUS_P2P_PORT, MEMPOOL_P2P_PORT, }; +use crate::deployments::distributed::RETRIES_FOR_L1_SERVICES; use crate::k8s::{ get_environment_ingress_internal, get_ingress, @@ -188,6 +190,17 @@ impl ServiceNameInner for HybridNodeServiceName { } } + fn get_retries(&self) -> usize { + match self { + Self::Core + | Self::HttpServer + | Self::Mempool + | Self::Gateway + | Self::SierraCompiler => DEFAULT_RETRIES, + Self::L1 => RETRIES_FOR_L1_SERVICES, + } + } + fn get_toleration(&self, environment: &Environment) -> Option { match environment { Environment::CloudK8s(cloud_env) => match self { @@ -198,7 +211,7 @@ impl ServiceNameInner for HybridNodeServiceName { CloudK8sEnvironment::Mainnet | CloudK8sEnvironment::SepoliaTestnet | CloudK8sEnvironment::StressTest => Some(Toleration::ApolloCoreServiceC2D56), - CloudK8sEnvironment::Potc2 => Some(Toleration::Batcher864), + CloudK8sEnvironment::PotcMock => Some(Toleration::Batcher864), }, HybridNodeServiceName::HttpServer | HybridNodeServiceName::Gateway @@ -264,30 +277,29 @@ impl ServiceNameInner for HybridNodeServiceName { fn get_resources(&self, environment: &Environment) -> Resources { match environment { Environment::CloudK8s(cloud_env) => match cloud_env { - CloudK8sEnvironment::SepoliaIntegration | CloudK8sEnvironment::UpgradeTest => { - match self { - HybridNodeServiceName::Core => { - Resources::new(Resource::new(2, 4), Resource::new(7, 14)) - } - HybridNodeServiceName::HttpServer => { - Resources::new(Resource::new(1, 2), Resource::new(4, 8)) - } - HybridNodeServiceName::Gateway => { - Resources::new(Resource::new(1, 2), Resource::new(2, 4)) - } - HybridNodeServiceName::L1 => { - Resources::new(Resource::new(1, 2), Resource::new(2, 4)) - } - HybridNodeServiceName::Mempool => { - Resources::new(Resource::new(1, 2), Resource::new(2, 4)) - } - HybridNodeServiceName::SierraCompiler => { - Resources::new(Resource::new(1, 2), Resource::new(2, 4)) - } + CloudK8sEnvironment::PotcMock + | CloudK8sEnvironment::SepoliaIntegration + | CloudK8sEnvironment::UpgradeTest => match self { + HybridNodeServiceName::Core => { + Resources::new(Resource::new(2, 4), Resource::new(7, 14)) } - } - CloudK8sEnvironment::Potc2 - | CloudK8sEnvironment::Mainnet + HybridNodeServiceName::HttpServer => { + Resources::new(Resource::new(1, 2), Resource::new(4, 8)) + } + HybridNodeServiceName::Gateway => { + Resources::new(Resource::new(1, 2), Resource::new(2, 4)) + } + HybridNodeServiceName::L1 => { + Resources::new(Resource::new(1, 2), Resource::new(2, 4)) + } + HybridNodeServiceName::Mempool => { + Resources::new(Resource::new(1, 2), Resource::new(2, 4)) + } + HybridNodeServiceName::SierraCompiler => { + Resources::new(Resource::new(1, 2), Resource::new(2, 4)) + } + }, + CloudK8sEnvironment::Mainnet | CloudK8sEnvironment::SepoliaTestnet | CloudK8sEnvironment::StressTest => match self { HybridNodeServiceName::Core => { @@ -871,6 +883,12 @@ fn hybrid_deployments(inputs: &DeploymentInputs) -> Vec { inputs.state_sync_type.clone(), consensus_p2p_bootstrap_config.clone(), mempool_p2p_bootstrap_config.clone(), + inputs.audited_libfuncs_only, + inputs.http_server_port, + inputs.monitoring_endpoint_config_port, + inputs.state_sync_config_rpc_config_port, + inputs.mempool_p2p_config_network_config_port, + inputs.consensus_manager_config_network_config_port, ), &inputs.node_namespace_format, &inputs.ingress_domain, diff --git a/crates/apollo_deployments/src/lib.rs b/crates/apollo_deployments/src/lib.rs index ac8a41f1c68..245b6633550 100644 --- a/crates/apollo_deployments/src/lib.rs +++ b/crates/apollo_deployments/src/lib.rs @@ -5,6 +5,7 @@ pub(crate) mod deployment; pub mod deployment_definitions; pub mod deployments; pub(crate) mod k8s; +pub(crate) mod replacers; pub(crate) mod scale_policy; pub mod service; #[cfg(test)] diff --git a/crates/apollo_deployments/src/replacers.rs b/crates/apollo_deployments/src/replacers.rs new file mode 100644 index 00000000000..140c6c19f09 --- /dev/null +++ b/crates/apollo_deployments/src/replacers.rs @@ -0,0 +1,30 @@ +use apollo_infra_utils::template::Template; +use serde_json::Value; +const REPLACER_FORMAT: &str = "$$$_{}_$$$"; + +pub(crate) fn insert_replacer_annotations(mut json: Value, pred: F) -> Value +where + F: Fn(&str, &Value) -> bool, +{ + let map = json.as_object_mut().expect("Should be a JSON object"); + + // Collect keys to avoid mutable borrow issues while iterating. + let keys: Vec = map.keys().cloned().collect(); + for key in keys { + let should_replace = { + // Evaluate predicate on current value + let value = map.get(&key).expect("Key must exist"); + pred(&key, value) + }; + + if should_replace { + map.insert(key.clone(), Value::String(format_key(key.clone()))); + } + } + + json +} + +fn format_key(key: String) -> String { + Template::new(REPLACER_FORMAT).format(&[&key]).to_uppercase().replace('.', "-").replace('#', "") +} diff --git a/crates/apollo_deployments/src/service.rs b/crates/apollo_deployments/src/service.rs index 1271132cb59..f2daecd07b7 100644 --- a/crates/apollo_deployments/src/service.rs +++ b/crates/apollo_deployments/src/service.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::fmt::Display; use std::iter::once; use std::net::{IpAddr, Ipv4Addr}; @@ -10,15 +10,19 @@ use apollo_infra_utils::dumping::serialize_to_file; #[cfg(test)] use apollo_infra_utils::dumping::serialize_to_file_test; use apollo_node_config::component_config::ComponentConfig; -use apollo_node_config::component_execution_config::ReactiveComponentExecutionConfig; +use apollo_node_config::component_execution_config::{ + ReactiveComponentExecutionConfig, + DEFAULT_URL, +}; use apollo_node_config::config_utils::{config_to_preset, prune_by_is_none}; use indexmap::IndexMap; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; -use serde_json::json; +use serde_json::{json, Value}; use strum::{Display, EnumVariantNames, IntoEnumIterator}; use strum_macros::{EnumDiscriminants, EnumIter, IntoStaticStr}; +use crate::config_override::{deployment_replacer_file_path, instance_replacer_file_path}; use crate::deployment::build_service_namespace_domain_address; use crate::deployment_definitions::{ ComponentConfigInService, @@ -40,6 +44,7 @@ use crate::k8s::{ Resources, Toleration, }; +use crate::replacers::insert_replacer_annotations; use crate::scale_policy::ScalePolicy; #[cfg(test)] use crate::test_utils::FIX_BINARY_NAME; @@ -84,8 +89,6 @@ impl Service { // TODO(Tsabary): reduce visibility of relevant functions and consts. - let service_file_path = node_service.get_service_file_path(); - let components_in_service = node_service .get_components_in_service() .into_iter() @@ -93,9 +96,26 @@ impl Service { .collect::>(); let config_paths = components_in_service .into_iter() - .chain(config_filenames) - .chain(once(service_file_path)) + .chain(config_filenames.clone()) + .chain(once(node_service.get_service_file_path())) + .collect(); + + // TODO(Tsabary): currently using the creation of the non-replacer service struct to DUMP + // the list of files of the replacer format. This should be triggered by a new flow. + let replacer_components_in_service = node_service + .get_components_in_service() + .into_iter() + .flat_map(|c| c.get_component_config_file_paths()) + .collect::>(); + let replacer_config_filenames: Vec = + vec![deployment_replacer_file_path(), instance_replacer_file_path()]; + let replacer_config_paths: Vec = replacer_components_in_service + .into_iter() + .chain(replacer_config_filenames) + .chain(once(node_service.get_replacer_service_file_path())) .collect(); + let replacer_deployment_file_path = node_service.replacer_deployment_file_path(); + serialize_to_file(&replacer_config_paths, &replacer_deployment_file_path); let controller = node_service.get_controller(); let scale_policy = node_service.get_scale_policy(); @@ -162,11 +182,24 @@ pub enum NodeService { Distributed(DistributedNodeServiceName), } +// TODO(Tsabary): move p2p ports from the application configs to the replacer format. + impl NodeService { + pub fn replacer_deployment_file_path(&self) -> String { + PathBuf::from(CONFIG_BASE_DIR) + .join(SERVICES_DIR_NAME) + .join(NodeType::from(self).get_folder_name()) + .join(format!("replacer_deployment_{}.json", self.as_inner())) + .to_string_lossy() + .to_string() + } + fn get_config_file_path(&self) -> String { - let mut name = self.as_inner().to_string(); - name.push_str(".json"); - name + format!("{}.json", self.as_inner()) + } + + fn get_replacer_config_file_path(&self) -> String { + format!("replacer_{}.json", self.as_inner()) } pub fn create_service( @@ -244,6 +277,8 @@ impl NodeService { self.as_inner().k8s_service_name() } + // TODO(Tsabary): deprecate this function after we complete the transition to the replacer + // format. fn get_service_file_path(&self) -> String { PathBuf::from(CONFIG_BASE_DIR) .join(SERVICES_DIR_NAME) @@ -253,7 +288,16 @@ impl NodeService { .to_string() } - fn get_components_in_service(&self) -> BTreeSet { + fn get_replacer_service_file_path(&self) -> String { + PathBuf::from(CONFIG_BASE_DIR) + .join(SERVICES_DIR_NAME) + .join(NodeType::from(self).get_folder_name()) + .join(self.get_replacer_config_file_path()) + .to_string_lossy() + .to_string() + } + + pub fn get_components_in_service(&self) -> BTreeSet { self.as_inner().get_components_in_service() } @@ -271,6 +315,8 @@ pub(crate) trait ServiceNameInner: Display { fn get_scale_policy(&self) -> ScalePolicy; + fn get_retries(&self) -> usize; + fn get_toleration(&self, environment: &Environment) -> Option; fn get_ingress( @@ -369,6 +415,27 @@ impl NodeType { } } + pub fn get_services_of_components( + &self, + component_type: ComponentConfigInService, + ) -> HashSet { + let services: HashSet<_> = self + .all_service_names() + .into_iter() + .filter(|node_service| { + node_service.get_components_in_service().contains(&component_type) + }) + .collect(); + + assert!( + !services.is_empty(), + "Expected at least one NodeService containing component type {:?}", + component_type + ); + + services + } + pub fn get_component_configs( &self, ports: Option>, @@ -392,8 +459,29 @@ impl NodeType { ComponentConfigsSerializationWrapper::new(component_config, components_in_service); let flattened = config_to_preset(&json!(wrapper.dump())); let pruned = prune_by_is_none(flattened); + // TODO(Tsabary): deprecate this section after we complete the transition to the + // replacer format. Dumping in the original format. let file_path = node_service.get_service_file_path(); writer(&pruned, &file_path); + + // Dumping in the replacer format. + + let replace_pred = |key: &str, value: &Value| { + // Condition 1: ports set by the infra: ".port" suffix and a non-zero integer value + let port_cond = + key.ends_with(".port") && value.as_i64().map(|n| n != 0).unwrap_or(false); + + // Condition 2: service urls: ".url" suffix and a non-localhost string value + let url_cond = key.ends_with(".url") + && value.as_str().map(|s| s != DEFAULT_URL).unwrap_or(false); + + port_cond || url_cond + }; + + let pruned_with_replacer_annotations = + insert_replacer_annotations(pruned, replace_pred); + let file_path = node_service.get_replacer_service_file_path(); + writer(&pruned_with_replacer_annotations, &file_path); } } @@ -429,12 +517,14 @@ pub(crate) trait GetComponentConfigs: ServiceNameInner { /// Returns a component execution config for a component that is accessed remotely. fn component_config_for_remote_service(&self, port: u16) -> ReactiveComponentExecutionConfig { let idle_connections = self.get_scale_policy().idle_connections(); + let retries = self.get_retries(); ReactiveComponentExecutionConfig::remote( self.k8s_service_name(), IpAddr::from(Ipv4Addr::UNSPECIFIED), port, ) .with_idle_connections(idle_connections) + .with_retries(retries) } fn component_config_pair(&self, port: u16) -> ComponentConfigPair { diff --git a/crates/apollo_gateway/src/errors.rs b/crates/apollo_gateway/src/errors.rs index a7044f2221a..96995b60b9d 100644 --- a/crates/apollo_gateway/src/errors.rs +++ b/crates/apollo_gateway/src/errors.rs @@ -15,6 +15,7 @@ use reqwest::StatusCode; use serde_json::{Error as SerdeError, Value}; use starknet_api::block::GasPrice; use starknet_api::executable_transaction::ValidateCompiledClassHashError; +use starknet_api::execution_resources::GasAmount; use starknet_api::transaction::fields::{AllResourceBounds, TransactionSignature}; use starknet_api::StarknetApiError; use thiserror::Error; @@ -77,6 +78,11 @@ pub enum StatelessTransactionValidatorError { "Max gas price is too low: {gas_price:?}, minimum required gas price: {min_gas_price:?}." )] MaxGasPriceTooLow { gas_price: GasPrice, min_gas_price: u128 }, + #[error( + "Max gas amount is too high: {gas_amount:?}, maximum allowed gas amount: \ + {max_gas_amount:?}." + )] + MaxGasAmountTooHigh { gas_amount: GasAmount, max_gas_amount: u64 }, } impl From for GatewaySpecError { @@ -97,7 +103,8 @@ impl From for GatewaySpecError { | StatelessTransactionValidatorError::SignatureTooLong { .. } | StatelessTransactionValidatorError::StarknetApiError(..) | StatelessTransactionValidatorError::ZeroResourceBounds { .. } - | StatelessTransactionValidatorError::MaxGasPriceTooLow { .. } => { + | StatelessTransactionValidatorError::MaxGasPriceTooLow { .. } + | StatelessTransactionValidatorError::MaxGasAmountTooHigh { .. } => { GatewaySpecError::ValidationFailure { data: e.to_string() } } } @@ -135,7 +142,6 @@ impl From for StarknetError { "StarknetErrorCode.ENTRY_POINTS_NOT_UNIQUELY_SORTED".to_string(), ) } - StatelessTransactionValidatorError::InvalidDataAvailabilityMode { .. } => // Error does not exist in deprecated GW. { @@ -143,7 +149,6 @@ impl From for StarknetError { "StarknetErrorCode.INVALID_DATA_AVAILABILITY_MODE".to_string(), ) } - StatelessTransactionValidatorError::InvalidSierraVersion(..) => // Error does not exist in deprecated GW. { @@ -151,12 +156,16 @@ impl From for StarknetError { "StarknetErrorCode.INVALID_SIERRA_VERSION".to_string(), ) } + StatelessTransactionValidatorError::MaxGasAmountTooHigh { .. } => { + StarknetErrorCode::UnknownErrorCode( + "StarknetErrorCode.MAX_GAS_AMOUNT_TOO_HIGH".to_string(), + ) + } StatelessTransactionValidatorError::NonEmptyField { .. } => // Error does not exist in deprecated GW. { StarknetErrorCode::UnknownErrorCode("StarknetErrorCode.NON_EMPTY_FIELD".to_string()) } - StatelessTransactionValidatorError::SignatureTooLong { .. } => { StarknetErrorCode::UnknownErrorCode( "StarknetErrorCode.SIGNATURE_TOO_LONG".to_string(), @@ -431,5 +440,11 @@ fn convert_sn_api_error(err: StarknetApiError) -> StarknetError { code: StarknetErrorCode::KnownErrorCode(KnownStarknetErrorCode::MalformedRequest), message: err.to_string(), }, + StarknetApiError::DeclareTransactionCasmHashMissMatch(err) => StarknetError { + code: StarknetErrorCode::KnownErrorCode( + KnownStarknetErrorCode::InvalidCompiledClassHash, + ), + message: err.to_string(), + }, } } diff --git a/crates/apollo_gateway/src/gateway.rs b/crates/apollo_gateway/src/gateway.rs index 870b0fc6b39..9ddfe720085 100644 --- a/crates/apollo_gateway/src/gateway.rs +++ b/crates/apollo_gateway/src/gateway.rs @@ -93,18 +93,18 @@ impl Gateway { tx: RpcTransaction, p2p_message_metadata: Option, ) -> GatewayResult { - debug!("Processing tx with signature: {:?}", tx.signature()); + debug!("Processing tx: {:?}", &tx); + let tx_signature = tx.signature().clone(); let is_p2p = p2p_message_metadata.is_some(); let start_time = std::time::Instant::now(); - let ret = self.add_tx_inner(&tx, p2p_message_metadata).await; + let ret = self.add_tx_inner(tx, p2p_message_metadata).await; let elapsed = start_time.elapsed().as_secs_f64(); debug!( "Processed tx with signature: {:?}. duration: {elapsed} sec, ret: {ret:?}, is_p2p: \ - {is_p2p}, tx: {:?}", - tx.signature(), - tx + {is_p2p}", + &tx_signature, ); ret @@ -112,10 +112,10 @@ impl Gateway { async fn add_tx_inner( &self, - tx: &RpcTransaction, + tx: RpcTransaction, p2p_message_metadata: Option, ) -> GatewayResult { - let mut metric_counters = GatewayMetricHandle::new(tx, &p2p_message_metadata); + let mut metric_counters = GatewayMetricHandle::new(&tx, &p2p_message_metadata); metric_counters.count_transaction_received(); if let RpcTransaction::Declare(ref declare_tx) = tx { @@ -126,17 +126,16 @@ impl Gateway { } // Perform stateless validations. - self.stateless_tx_validator.validate(tx)?; + self.stateless_tx_validator.validate(&tx)?; let tx_signature = tx.signature().clone(); - let internal_tx = self - .transaction_converter - .convert_rpc_tx_to_internal_rpc_tx(tx.clone()) - .await - .map_err(|e| { - warn!("Failed to convert RPC transaction to internal RPC transaction: {}", e); - transaction_converter_err_to_deprecated_gw_err(&tx_signature, e) - })?; + let internal_tx = + self.transaction_converter.convert_rpc_tx_to_internal_rpc_tx(tx).await.map_err( + |e| { + warn!("Failed to convert RPC transaction to internal RPC transaction: {}", e); + transaction_converter_err_to_deprecated_gw_err(&tx_signature, e) + }, + )?; let executable_tx = self .transaction_converter @@ -162,8 +161,7 @@ impl Gateway { Ok(Err(starknet_err)) => { info!( "Gateway validation failed for tx with signature: {:?} with error: {}", - tx.signature(), - starknet_err + &tx_signature, starknet_err ); metric_counters.record_add_tx_failure(&starknet_err); return Err(starknet_err); @@ -171,7 +169,7 @@ impl Gateway { Err(join_err) => { let err = StarknetError::internal_with_signature_logging( "Failed to process tx", - tx.signature(), + &tx_signature, join_err, ); metric_counters.record_add_tx_failure(&err); @@ -185,10 +183,8 @@ impl Gateway { args: AddTransactionArgs::new(internal_tx, nonce), p2p_message_metadata, }; - match mempool_client_result_to_deprecated_gw_result( - tx.signature(), - self.mempool_client.add_tx(add_tx_args).await, - ) { + let mempool_client_result = self.mempool_client.add_tx(add_tx_args).await; + match mempool_client_result_to_deprecated_gw_result(&tx_signature, mempool_client_result) { Ok(()) => {} Err(e) => { metric_counters.record_add_tx_failure(&e); diff --git a/crates/apollo_gateway/src/metrics.rs b/crates/apollo_gateway/src/metrics.rs index 127bc992971..942c9525ac6 100644 --- a/crates/apollo_gateway/src/metrics.rs +++ b/crates/apollo_gateway/src/metrics.rs @@ -45,7 +45,7 @@ define_metrics!( LabeledMetricCounter { GATEWAY_ADD_TX_FAILURE, "gateway_add_tx_failure", "Counter of add_tx failures by reason", init = 0 , labels = ADD_TX_FAILURE_LABELS}, MetricHistogram { GATEWAY_ADD_TX_LATENCY, "gateway_add_tx_latency", "Latency of gateway add_tx function in secs" }, MetricHistogram { GATEWAY_VALIDATE_TX_LATENCY, "gateway_validate_tx_latency", "Latency of gateway validate function in secs" }, - MetricHistogram { GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_MICROS, "gateway_validate_stateful_tx_storage_micros", "Total time spent in storage operations in micros during stateful tx validation" }, + MetricHistogram { GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_TIME, "gateway_validate_stateful_tx_storage_time", "Total time spent in storage operations in secs during stateful tx validation" }, MetricHistogram { GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_OPERATIONS, "gateway_validate_stateful_tx_storage_operations", "Total number of storage operations during stateful tx validation"}, }, ); @@ -90,6 +90,7 @@ pub enum GatewayAddTxFailureReason { NonEmptyField, SignatureTooLong, StarknetApiError, + MaxGasAmountTooHigh, NonceTooLarge, BlockedTransactionType, InternalError, @@ -218,6 +219,8 @@ fn map_starknet_error_to_gateway_add_tx_failure_reason( GatewayAddTxFailureReason::SignatureTooLong } else if s.contains("STARKNET_API_ERROR") { GatewayAddTxFailureReason::StarknetApiError + } else if s.contains("MAX_GAS_AMOUNT_TOO_HIGH") { + GatewayAddTxFailureReason::MaxGasAmountTooHigh } else if s.contains("NONCE_TOO_LARGE") { GatewayAddTxFailureReason::NonceTooLarge } else if s.contains("InternalError") { @@ -247,6 +250,6 @@ pub(crate) fn register_metrics() { GATEWAY_ADD_TX_FAILURE.register(); GATEWAY_ADD_TX_LATENCY.register(); GATEWAY_VALIDATE_TX_LATENCY.register(); - GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_MICROS.register(); + GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_TIME.register(); GATEWAY_VALIDATE_STATEFUL_TX_STORAGE_OPERATIONS.register(); } diff --git a/crates/apollo_gateway/src/stateful_transaction_validator.rs b/crates/apollo_gateway/src/stateful_transaction_validator.rs index dd7b4739163..058e109a793 100644 --- a/crates/apollo_gateway/src/stateful_transaction_validator.rs +++ b/crates/apollo_gateway/src/stateful_transaction_validator.rs @@ -186,19 +186,27 @@ impl StatefulTransactionValidator { executable_tx: &ExecutableTransaction, account_nonce: Nonce, ) -> StatefulTransactionValidatorResult<()> { - if !self.is_valid_nonce(executable_tx, account_nonce) { + let (valid, max_allowed_nonce_gap) = self.is_valid_nonce(executable_tx, account_nonce); + if !valid { let tx_nonce = executable_tx.nonce(); - debug!( - "Transaction nonce is invalid. Transaction nonce: {tx_nonce}, account_nonce: \ - {account_nonce}", - ); + let message = if max_allowed_nonce_gap == 0 { + format!( + "Invalid transaction nonce. Expected: nonce = {account_nonce}, got: \ + {tx_nonce}." + ) + } else { + let max_allowed_nonce = Nonce(account_nonce.0 + Felt::from(max_allowed_nonce_gap)); + format!( + "Invalid transaction nonce. Expected: {account_nonce} <= nonce <= \ + {max_allowed_nonce}, got: {tx_nonce}.", + ) + }; + debug!("{message}"); return Err(StarknetError { code: StarknetErrorCode::KnownErrorCode( KnownStarknetErrorCode::InvalidTransactionNonce, ), - message: format!( - "Invalid transaction nonce. Expected: {account_nonce}, got: {tx_nonce}." - ), + message, }); } Ok(()) @@ -228,19 +236,26 @@ impl StatefulTransactionValidator { Ok(()) } - fn is_valid_nonce(&self, executable_tx: &ExecutableTransaction, account_nonce: Nonce) -> bool { + fn is_valid_nonce( + &self, + executable_tx: &ExecutableTransaction, + account_nonce: Nonce, + ) -> (bool, u32) { let incoming_tx_nonce = executable_tx.nonce(); // Declare transactions must have the same nonce as the account nonce. if self.config.reject_future_declare_txs && matches!(executable_tx, ExecutableTransaction::Declare(_)) { - return incoming_tx_nonce == account_nonce; + return (incoming_tx_nonce == account_nonce, 0); } let max_allowed_nonce = Nonce(account_nonce.0 + Felt::from(self.config.max_allowed_nonce_gap)); - account_nonce <= incoming_tx_nonce && incoming_tx_nonce <= max_allowed_nonce + ( + account_nonce <= incoming_tx_nonce && incoming_tx_nonce <= max_allowed_nonce, + self.config.max_allowed_nonce_gap, + ) } // TODO(Arni): Consider running this validation for all gas prices. diff --git a/crates/apollo_gateway/src/stateless_transaction_validator.rs b/crates/apollo_gateway/src/stateless_transaction_validator.rs index 257719a1302..9d801b754a3 100644 --- a/crates/apollo_gateway/src/stateless_transaction_validator.rs +++ b/crates/apollo_gateway/src/stateless_transaction_validator.rs @@ -70,6 +70,15 @@ impl StatelessTransactionValidator { }); } + // TODO(Arni): Consider adding a validation for max_l2_gas_amount for declare. + if let RpcTransaction::Declare(_) = tx { + } else if resource_bounds.l2_gas.max_amount.0 > self.config.max_l2_gas_amount { + return Err(StatelessTransactionValidatorError::MaxGasAmountTooHigh { + gas_amount: resource_bounds.l2_gas.max_amount, + max_gas_amount: self.config.max_l2_gas_amount, + }); + } + Ok(()) } diff --git a/crates/apollo_gateway/src/stateless_transaction_validator_test.rs b/crates/apollo_gateway/src/stateless_transaction_validator_test.rs index 5b90dfc0757..465ae3c5889 100644 --- a/crates/apollo_gateway/src/stateless_transaction_validator_test.rs +++ b/crates/apollo_gateway/src/stateless_transaction_validator_test.rs @@ -8,6 +8,7 @@ use rstest::rstest; use starknet_api::block::GasPrice; use starknet_api::core::{EntryPointSelector, L2_ADDRESS_UPPER_BOUND}; use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::execution_resources::GasAmount; use starknet_api::rpc_transaction::EntryPointByType; use starknet_api::state::{EntryPoint, SierraContractClass}; use starknet_api::test_utils::declare::rpc_declare_tx; @@ -43,10 +44,11 @@ static DEFAULT_VALIDATOR_CONFIG_FOR_TESTING: LazyLock>, #[case] sync_client_result: StateSyncClientResult, #[case] expected_result: StateResult, + #[case] class_hash: ClassHash, ) { let mut mock_state_sync_client = MockStateSyncClient::new(); let mut mock_class_manager_client = MockClassManagerClient::new(); - let class_hash = *DUMMY_CLASS_HASH; let block_number = BlockNumber(1); mock_class_manager_client @@ -285,6 +288,7 @@ async fn test_get_compiled_class_panics_when_class_exists_in_sync_but_not_in_cla Ok(None), Ok(true), Err(StateError::UndeclaredClassHash(*DUMMY_CLASS_HASH)), + *DUMMY_CLASS_HASH, ) .await; } diff --git a/crates/apollo_gateway_config/src/config.rs b/crates/apollo_gateway_config/src/config.rs index b09f452f198..3e63ae59519 100644 --- a/crates/apollo_gateway_config/src/config.rs +++ b/crates/apollo_gateway_config/src/config.rs @@ -1,6 +1,9 @@ use std::collections::BTreeMap; -use std::str::FromStr; +use apollo_config::converters::{ + deserialize_comma_separated_str, + serialize_optional_comma_separated, +}; use apollo_config::dumping::{ prepend_sub_config_name, ser_optional_param, @@ -10,7 +13,7 @@ use apollo_config::dumping::{ use apollo_config::{ParamPath, ParamPrivacyInput, SerializedParam}; use blockifier::blockifier_versioned_constants::VersionedConstantsOverrides; use blockifier::context::ChainInfo; -use serde::{de, Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use starknet_api::core::{ContractAddress, Nonce}; use starknet_types_core::felt::Felt; use validator::Validate; @@ -25,7 +28,7 @@ pub struct GatewayConfig { pub stateful_tx_validator_config: StatefulTransactionValidatorConfig, pub chain_info: ChainInfo, pub block_declare: bool, - #[serde(default, deserialize_with = "deserialize_optional_contract_addresses")] + #[serde(default, deserialize_with = "deserialize_comma_separated_str")] pub authorized_declarer_accounts: Option>, } @@ -47,9 +50,7 @@ impl SerializeConfig for GatewayConfig { )); dump.extend(prepend_sub_config_name(self.chain_info.dump(), "chain_info")); dump.extend(ser_optional_param( - &self.authorized_declarer_accounts.as_ref().map(|accounts| { - accounts.iter().map(|addr| addr.0.to_string()).collect::>().join(",") - }), + &serialize_optional_comma_separated(&self.authorized_declarer_accounts), "".to_string(), "authorized_declarer_accounts", "Authorized declarer accounts. If set, only these accounts can declare new contracts. \ @@ -69,47 +70,14 @@ impl GatewayConfig { } } -fn deserialize_optional_contract_addresses<'de, D>( - de: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let raw: String = match Option::deserialize(de)? { - Some(addresses) => addresses, - None => return Ok(None), - }; - - if raw.is_empty() { - return Err(de::Error::custom( - "Empty string is not a valid input for contract addresses. The config field \ - `gateway_config.authorized_declarer_accounts.#is_none` is false and should be true \ - if you don't want to use this feature.", - )); - } - - let mut result = Vec::new(); - for addresses_str in raw.split(',') { - let felt = Felt::from_str(addresses_str).map_err(|err| { - de::Error::custom(format!("Failed to parse Felt from '{addresses_str}': {err}")) - })?; - - let addr = ContractAddress::try_from(felt).map_err(|err| { - de::Error::custom(format!("Invalid contract address '{addresses_str}': {err}")) - })?; - - result.push(addr); - } - - Ok(Some(result)) -} - #[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)] pub struct StatelessTransactionValidatorConfig { // If true, ensures that at least one resource bound (L1, L2, or L1 data) is greater than zero. pub validate_resource_bounds: bool, - // TODO(AlonH): Remove this field and use the one from the versioned constants. + // TODO(AlonH): Remove the `min_gas_price` field from this struct and use the one from the + // versioned constants. pub min_gas_price: u128, + pub max_l2_gas_amount: u64, pub max_calldata_length: usize, pub max_signature_length: usize, @@ -125,6 +93,7 @@ impl Default for StatelessTransactionValidatorConfig { StatelessTransactionValidatorConfig { validate_resource_bounds: true, min_gas_price: 3_000_000_000, + max_l2_gas_amount: 1_200_000_000, max_calldata_length: 4000, max_signature_length: 4000, max_contract_bytecode_size: 81920, @@ -175,6 +144,12 @@ impl SerializeConfig for StatelessTransactionValidatorConfig { "Minimum gas price for transactions.", ParamPrivacyInput::Public, ), + ser_param( + "max_l2_gas_amount", + &self.max_l2_gas_amount, + "Maximum allowed L2 gas amount for transactions.", + ParamPrivacyInput::Public, + ), ]); vec![ members, diff --git a/crates/apollo_http_server/src/deprecated_gateway_transaction.rs b/crates/apollo_http_server/src/deprecated_gateway_transaction.rs index cb5281c5e7b..f186f254718 100644 --- a/crates/apollo_http_server/src/deprecated_gateway_transaction.rs +++ b/crates/apollo_http_server/src/deprecated_gateway_transaction.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; #[cfg(any(feature = "testing", test))] use starknet_api::compression_utils::compress_and_encode; -use starknet_api::compression_utils::{decode_and_decompress, CompressionError}; +use starknet_api::compression_utils::{decode_and_decompress_with_size_limit, CompressionError}; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; use starknet_api::rpc_transaction::{ @@ -282,7 +282,12 @@ impl TryFrom for SierraContractClass { fn try_from( rest_sierra_contract_class: DeprecatedGatewaySierraContractClass, ) -> Result { - let sierra_program = decode_and_decompress(&rest_sierra_contract_class.sierra_program)?; + // TODO(dan): use config for this. + const MAX_SIERRA_PROGRAM_SIZE: usize = 4 * 1024 * 1024; // 4MB + let sierra_program = decode_and_decompress_with_size_limit( + &rest_sierra_contract_class.sierra_program, + MAX_SIERRA_PROGRAM_SIZE, + )?; Ok(SierraContractClass { sierra_program, contract_class_version: rest_sierra_contract_class.contract_class_version, diff --git a/crates/apollo_http_server/src/http_server.rs b/crates/apollo_http_server/src/http_server.rs index d30dd911521..30ae23035ed 100644 --- a/crates/apollo_http_server/src/http_server.rs +++ b/crates/apollo_http_server/src/http_server.rs @@ -27,7 +27,7 @@ use serde::de::Error; use starknet_api::rpc_transaction::RpcTransaction; use starknet_api::serde_utils::bytes_from_hex_str; use starknet_api::transaction::fields::ValidResourceBounds; -use tracing::{debug, info, instrument}; +use tracing::{debug, info, instrument, warn}; use crate::deprecated_gateway_transaction::DeprecatedGatewayTransactionV3; use crate::errors::{HttpServerError, HttpServerRunError}; @@ -239,7 +239,13 @@ fn record_added_transactions(add_tx_result: &HttpServerResult, re ); ADDED_TRANSACTIONS_SUCCESS.increment(1); } - Err(err) => increment_failure_metrics(err), + Err(err) => { + warn!( + error = %err, + "Failed to record transaction" + ); + increment_failure_metrics(err); + } } } diff --git a/crates/apollo_infra/Cargo.toml b/crates/apollo_infra/Cargo.toml index cef3288c080..a49efe1af34 100644 --- a/crates/apollo_infra/Cargo.toml +++ b/crates/apollo_infra/Cargo.toml @@ -27,7 +27,7 @@ time = { workspace = true, features = ["macros"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tower = { workspace = true, features = ["limit", "util"] } tracing.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter", "time"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "json", "time"] } validator.workspace = true [dev-dependencies] diff --git a/crates/apollo_infra/src/component_client/mod.rs b/crates/apollo_infra/src/component_client/mod.rs index 558a849fbb7..6dd5395c726 100644 --- a/crates/apollo_infra/src/component_client/mod.rs +++ b/crates/apollo_infra/src/component_client/mod.rs @@ -1,6 +1,6 @@ mod definitions; mod local_component_client; -mod remote_component_client; +pub mod remote_component_client; pub use definitions::*; pub use local_component_client::*; diff --git a/crates/apollo_infra/src/component_client/remote_component_client.rs b/crates/apollo_infra/src/component_client/remote_component_client.rs index 49e294e113f..c498b204990 100644 --- a/crates/apollo_infra/src/component_client/remote_component_client.rs +++ b/crates/apollo_infra/src/component_client/remote_component_client.rs @@ -22,7 +22,7 @@ use crate::metrics::RemoteClientMetrics; use crate::requests::LabeledRequest; use crate::serde_utils::SerdeWrapper; -const DEFAULT_RETRIES: usize = 150; +pub const DEFAULT_RETRIES: usize = 150; const DEFAULT_IDLE_CONNECTIONS: usize = 10; const DEFAULT_IDLE_TIMEOUT_MS: u64 = 30000; const DEFAULT_MAX_RETRY_INTERVAL_MS: u64 = 1000; diff --git a/crates/apollo_infra/src/trace_util.rs b/crates/apollo_infra/src/trace_util.rs index e32e9e1cea4..cfd699657d3 100644 --- a/crates/apollo_infra/src/trace_util.rs +++ b/crates/apollo_infra/src/trace_util.rs @@ -14,19 +14,20 @@ pub static PID: std::sync::LazyLock = std::sync::LazyLock::new(std::process pub async fn configure_tracing() { TRACING_INITIALIZED .get_or_init(|| async { - // Use default time formatting with subsecond precision limited to three digits. + // Use default time formatting with sub-second precision limited to three digits. let time_format = format_description!( "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z" ); let timer = UtcTime::new(time_format); let fmt_layer = fmt::layer() - .compact() + .json() .with_timer(timer) .with_target(false) // No module name. // Instead, file name and line number. .with_file(true) - .with_line_number(true); + .with_line_number(true) + .flatten_event(true); let level_filter_layer = EnvFilter::builder() .with_default_directive(DEFAULT_LEVEL.into()) diff --git a/crates/apollo_infra_utils/Cargo.toml b/crates/apollo_infra_utils/Cargo.toml index 4b08d7f5310..733133cdd56 100644 --- a/crates/apollo_infra_utils/Cargo.toml +++ b/crates/apollo_infra_utils/Cargo.toml @@ -7,7 +7,7 @@ license-file.workspace = true description = "Infrastructure utility." [features] -testing = ["colored", "dep:assert-json-diff", "socket2", "tempfile"] +testing = ["colored", "dep:assert-json-diff", "socket2", "strum", "tempfile"] [lints] workspace = true @@ -20,6 +20,7 @@ num_enum.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true socket2 = { workspace = true, optional = true } +strum = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } thiserror.workspace = true tokio = { workspace = true, features = ["process", "rt", "time"] } @@ -32,6 +33,7 @@ nix.workspace = true pretty_assertions.workspace = true rstest.workspace = true socket2.workspace = true +strum.workspace = true tempfile.workspace = true tokio = { workspace = true, features = ["macros", "rt", "signal", "sync"] } toml.workspace = true diff --git a/crates/apollo_infra_utils/src/dumping.rs b/crates/apollo_infra_utils/src/dumping.rs index 517721c886c..8f78d74b7d7 100644 --- a/crates/apollo_infra_utils/src/dumping.rs +++ b/crates/apollo_infra_utils/src/dumping.rs @@ -15,14 +15,14 @@ use crate::path::resolve_project_relative_path; use crate::test_utils::assert_json_eq; #[cfg(any(feature = "testing", test))] -pub fn serialize_to_file_test(data: T, file_path: &str, fix_binary_name: &str) { +pub fn serialize_to_file_test(data: &T, file_path: &str, fix_binary_name: &str) { let file_path = resolve_project_relative_path("").unwrap().join(file_path); let file = File::open(&file_path).unwrap_or_else(|err| { panic!("Failed to open file '{}': {}", file_path.display(), err); }); let loaded_data: Value = from_reader(file).unwrap(); let serialized_data = - to_value(&data).expect("Should have been able to serialize the data to JSON"); + to_value(data).expect("Should have been able to serialize the data to JSON"); let error_message = format!( "{}{}{}\n{}", @@ -36,7 +36,7 @@ pub fn serialize_to_file_test(data: T, file_path: &str, fix_binary assert_json_eq(&loaded_data, &serialized_data, error_message); } -pub fn serialize_to_file(data: T, file_path: &str) { +pub fn serialize_to_file(data: &T, file_path: &str) { // Ensure the parent directory exists if let Some(parent) = PathBuf::from(file_path).parent() { create_dir_all(parent).unwrap_or_else(|err| { @@ -51,7 +51,7 @@ pub fn serialize_to_file(data: T, file_path: &str) { let mut writer = BufWriter::new(file); // Add config as JSON content to writer. - to_writer_pretty(&mut writer, &data) + to_writer_pretty(&mut writer, data) .expect("Should have been able to serialize input data to JSON."); // Add an extra newline after the JSON content. diff --git a/crates/apollo_infra_utils/src/test_utils.rs b/crates/apollo_infra_utils/src/test_utils.rs index 2a382410108..859ae6d1d2e 100644 --- a/crates/apollo_infra_utils/src/test_utils.rs +++ b/crates/apollo_infra_utils/src/test_utils.rs @@ -4,11 +4,13 @@ use assert_json_diff::{assert_json_matches_no_panic, CompareMode, Config}; use num_enum::IntoPrimitive; use serde::Serialize; use socket2::{Domain, Socket, Type}; +use strum::EnumCount; use tracing::{info, instrument}; const PORTS_PER_INSTANCE: u16 = 60; pub const MAX_NUMBER_OF_INSTANCES_PER_TEST: u16 = 28; -const MAX_NUMBER_OF_TESTS: u16 = 11; +#[allow(clippy::as_conversions)] +const MAX_NUMBER_OF_TESTS: u16 = TestIdentifier::COUNT as u16; const BASE_PORT: u16 = 11000; // Ensure available ports don't exceed u16::MAX. @@ -21,7 +23,7 @@ const _: () = { }; #[repr(u16)] -#[derive(Debug, Copy, Clone, IntoPrimitive)] +#[derive(Debug, Copy, Clone, IntoPrimitive, EnumCount)] // TODO(Nadin): Come up with a better name for this enum. pub enum TestIdentifier { EndToEndFlowTest, @@ -29,6 +31,7 @@ pub enum TestIdentifier { EndToEndFlowTestManyTxs, EndToEndFlowTestCustomSyscallInvokeTxs, EndToEndFlowTestCustomCairo0Txs, + RevertedL1HandlerTx, InfraUnitTests, PositiveFlowIntegrationTest, RestartFlowIntegrationTest, diff --git a/crates/apollo_integration_tests/Cargo.toml b/crates/apollo_integration_tests/Cargo.toml index 3437d36ebe6..4cd96c1b9f4 100644 --- a/crates/apollo_integration_tests/Cargo.toml +++ b/crates/apollo_integration_tests/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] alloy.workspace = true anyhow.workspace = true +apollo_base_layer_tests.workspace = true apollo_batcher.workspace = true apollo_batcher_config.workspace = true apollo_class_manager = { workspace = true, features = ["testing"] } @@ -31,6 +32,7 @@ apollo_http_server = { workspace = true, features = ["testing"] } apollo_http_server_config.workspace = true apollo_infra = { workspace = true, features = ["testing"] } apollo_infra_utils = { workspace = true, features = ["testing"] } +apollo_l1_endpoint_monitor.workspace = true apollo_l1_endpoint_monitor_config.workspace = true apollo_l1_gas_price.workspace = true apollo_l1_gas_price_provider_config.workspace = true @@ -39,6 +41,7 @@ apollo_l1_provider_config.workspace = true apollo_l1_scraper_config.workspace = true apollo_mempool_config.workspace = true apollo_mempool_p2p_config.workspace = true +apollo_metrics.workspace = true apollo_monitoring_endpoint = { workspace = true, features = ["testing"] } apollo_monitoring_endpoint_config.workspace = true apollo_network = { workspace = true, features = ["testing"] } @@ -75,9 +78,12 @@ url.workspace = true [dev-dependencies] apollo_infra.workspace = true +apollo_l1_provider.workspace = true +apollo_l1_provider_types = { workspace = true, features = ["testing"] } futures.workspace = true metrics.workspace = true metrics-exporter-prometheus.workspace = true +mockall.workspace = true pretty_assertions.workspace = true rstest.workspace = true diff --git a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_central_and_p2p_sync_flow.rs b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_central_and_p2p_sync_flow.rs index a90d0a1b9b5..0faf0945aaf 100644 --- a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_central_and_p2p_sync_flow.rs +++ b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_central_and_p2p_sync_flow.rs @@ -18,12 +18,14 @@ async fn main() { const N_CONSOLIDATED_SEQUENCERS: usize = 2; /// The number of distributed remote sequencers that participate in the test. const N_DISTRIBUTED_SEQUENCERS: usize = 0; - + /// The number of hybrid sequencers that participate in the test. + const N_HYBRID_SEQUENCERS: usize = 0; const CENTRAL_SYNC_NODE: usize = 1; let mut integration_test_manager = IntegrationTestManager::new( N_CONSOLIDATED_SEQUENCERS, N_DISTRIBUTED_SEQUENCERS, + N_HYBRID_SEQUENCERS, None, TestIdentifier::SyncFlowIntegrationTest, ) diff --git a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_positive_flow.rs b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_positive_flow.rs index 29c7ab0892b..1c80c92402b 100644 --- a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_positive_flow.rs +++ b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_positive_flow.rs @@ -13,12 +13,15 @@ async fn main() { /// The number of consolidated local sequencers that participate in the test. const N_CONSOLIDATED_SEQUENCERS: usize = 3; /// The number of distributed remote sequencers that participate in the test. - const N_DISTRIBUTED_SEQUENCERS: usize = 2; + const N_DISTRIBUTED_SEQUENCERS: usize = 1; + /// The number of hybrid sequencers that participate in the test. + const N_HYBRID_SEQUENCERS: usize = 1; // Get the sequencer configurations. let mut integration_test_manager = IntegrationTestManager::new( N_CONSOLIDATED_SEQUENCERS, N_DISTRIBUTED_SEQUENCERS, + N_HYBRID_SEQUENCERS, None, TestIdentifier::PositiveFlowIntegrationTest, ) diff --git a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_restart_flow.rs b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_restart_flow.rs index 887e1438148..8c97454fc5b 100644 --- a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_restart_flow.rs +++ b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_restart_flow.rs @@ -25,7 +25,9 @@ async fn main() { /// The number of consolidated local sequencers that participate in the test. const N_CONSOLIDATED_SEQUENCERS: usize = 1; /// The number of distributed remote sequencers that participate in the test. - const N_DISTRIBUTED_SEQUENCERS: usize = 2; + const N_DISTRIBUTED_SEQUENCERS: usize = 1; + /// The number of hybrid sequencers that participate in the test. + const N_HYBRID_SEQUENCERS: usize = 1; // The indices of the nodes that we will be shutting down. // The test restarts a hybrid node and shuts down a non-consolidated (hybrid/distributed) node. const RESTART_NODE: usize = N_CONSOLIDATED_SEQUENCERS; @@ -35,6 +37,7 @@ async fn main() { let mut integration_test_manager = IntegrationTestManager::new( N_CONSOLIDATED_SEQUENCERS, N_DISTRIBUTED_SEQUENCERS, + N_HYBRID_SEQUENCERS, None, TestIdentifier::RestartFlowIntegrationTest, ) diff --git a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_revert_flow.rs b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_revert_flow.rs index ff3f7dd3372..363817dfe0a 100644 --- a/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_revert_flow.rs +++ b/crates/apollo_integration_tests/src/bin/sequencer_node_end_to_end_integration_tests/integration_test_revert_flow.rs @@ -21,10 +21,11 @@ async fn main() { assert!(BLOCK_TO_REVERT_FROM < BLOCK_TO_WAIT_FOR_AFTER_REVERT); const N_INVOKE_TXS: usize = 50; - // TODO(Arni): handle L1 handlers in this scenario. - const N_L1_HANDLER_TXS: usize = 0; + const N_L1_HANDLER_TXS: usize = 5; /// The number of consolidated local sequencers that participate in the test. const N_CONSOLIDATED_SEQUENCERS: usize = 5; + /// The number of hybrid sequencers that participate in the test. + const N_HYBRID_SEQUENCERS: usize = 0; /// The number of distributed remote sequencers that participate in the test. const N_DISTRIBUTED_SEQUENCERS: usize = 0; @@ -36,6 +37,7 @@ async fn main() { let mut integration_test_manager = IntegrationTestManager::new( N_CONSOLIDATED_SEQUENCERS, N_DISTRIBUTED_SEQUENCERS, + N_HYBRID_SEQUENCERS, None, TestIdentifier::RevertFlowIntegrationTest, ) diff --git a/crates/apollo_integration_tests/src/bin/sequencer_node_setup.rs b/crates/apollo_integration_tests/src/bin/sequencer_node_setup.rs index 8e3f641cb4c..1e00661d67a 100644 --- a/crates/apollo_integration_tests/src/bin/sequencer_node_setup.rs +++ b/crates/apollo_integration_tests/src/bin/sequencer_node_setup.rs @@ -27,6 +27,7 @@ async fn main() { let test_manager = IntegrationTestManager::new( args.n_consolidated, args.n_distributed, + args.n_hybrid, Some(custom_paths), // TODO(Tsabary/Nadin): add a different identifier. TestIdentifier::PositiveFlowIntegrationTest, @@ -58,6 +59,9 @@ struct Args { #[arg(long)] n_distributed: usize, + #[arg(long)] + n_hybrid: usize, + #[arg(long)] output_base_dir: String, diff --git a/crates/apollo_integration_tests/src/bin/sequencer_simulator.rs b/crates/apollo_integration_tests/src/bin/sequencer_simulator.rs index ba8a81d4b30..da432f06b32 100644 --- a/crates/apollo_integration_tests/src/bin/sequencer_simulator.rs +++ b/crates/apollo_integration_tests/src/bin/sequencer_simulator.rs @@ -13,9 +13,12 @@ use apollo_integration_tests::utils::{ }; use clap::Parser; use mempool_test_utils::starknet_api_test_utils::MultiAccountTransactionGenerator; -use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerConfig; +use papyrus_base_layer::ethereum_base_layer_contract::{ + EthereumBaseLayerConfig, + EthereumBaseLayerContract, + Starknet, +}; use papyrus_base_layer::test_utils::{ - deploy_starknet_l1_contract, make_block_history_on_anvil, DEFAULT_ANVIL_L1_DEPLOYED_ADDRESS, }; @@ -135,7 +138,9 @@ async fn initialize_anvil_state(sender_address: Address, receiver_address: Addre let (base_layer_config, base_layer_url) = build_base_layer_config_for_testing(); - deploy_starknet_l1_contract(base_layer_config.clone(), &base_layer_url).await; + let ethereum_base_layer_contract = + EthereumBaseLayerContract::new(base_layer_config.clone(), base_layer_url.clone()); + Starknet::deploy(ethereum_base_layer_contract.contract.provider().clone()).await.unwrap(); make_block_history_on_anvil( sender_address, diff --git a/crates/apollo_integration_tests/src/executable_setup.rs b/crates/apollo_integration_tests/src/executable_setup.rs index dca6e25770b..f41371d659a 100644 --- a/crates/apollo_integration_tests/src/executable_setup.rs +++ b/crates/apollo_integration_tests/src/executable_setup.rs @@ -31,19 +31,18 @@ impl NodeExecutionId { pub fn build_path(&self, base: &Path) -> PathBuf { base.join(format!("node_{}", self.node_index)) - .join(format!("executable_{}", self.executable_index)) } } impl std::fmt::Display for NodeExecutionId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Node id {} part {}", self.node_index, self.executable_index) + write!(f, "Node id {}", self.node_index) } } impl From for NodeRunner { fn from(val: NodeExecutionId) -> Self { - NodeRunner::new(val.node_index, val.executable_index) + NodeRunner::new(val.node_index) } } diff --git a/crates/apollo_integration_tests/src/flow_test_setup.rs b/crates/apollo_integration_tests/src/flow_test_setup.rs index ad1faf1c148..51536f24bf2 100644 --- a/crates/apollo_integration_tests/src/flow_test_setup.rs +++ b/crates/apollo_integration_tests/src/flow_test_setup.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::net::SocketAddr; use std::sync::Arc; -use alloy::node_bindings::AnvilInstance; +use apollo_base_layer_tests::anvil_base_layer::AnvilBaseLayer; use apollo_consensus_manager_config::config::ConsensusManagerConfig; use apollo_http_server::test_utils::HttpTestClient; use apollo_http_server_config::config::HttpServerConfig; @@ -31,23 +31,24 @@ use mempool_test_utils::starknet_api_test_utils::{ AccountTransactionGenerator, MultiAccountTransactionGenerator, }; -use papyrus_base_layer::ethereum_base_layer_contract::{ - EthereumBaseLayerConfig, - L1ToL2MessageArgs, - StarknetL1Contract, -}; +use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerConfig; use papyrus_base_layer::test_utils::{ - ethereum_base_layer_config_for_anvil, make_block_history_on_anvil, - spawn_anvil_and_deploy_starknet_l1_contract, - DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX, + ARBITRARY_ANVIL_L1_ACCOUNT_ADDRESS, + OTHER_ARBITRARY_ANVIL_L1_ACCOUNT_ADDRESS, }; +use papyrus_base_layer::BaseLayerContract; use starknet_api::block::BlockNumber; use starknet_api::consensus_transaction::ConsensusTransaction; use starknet_api::core::{ChainId, ContractAddress}; use starknet_api::execution_resources::GasAmount; use starknet_api::rpc_transaction::RpcTransaction; -use starknet_api::transaction::{TransactionHash, TransactionHasher, TransactionVersion}; +use starknet_api::transaction::{ + L1HandlerTransaction, + TransactionHash, + TransactionHasher, + TransactionVersion, +}; use starknet_types_core::felt::Felt; use tokio::sync::Mutex; use tracing::{debug, instrument, Instrument}; @@ -77,10 +78,9 @@ pub struct FlowTestSetup { pub sequencer_0: FlowSequencerSetup, pub sequencer_1: FlowSequencerSetup, - // Handle for L1 server: the server is dropped when handle is dropped. - #[allow(dead_code)] - l1_handle: AnvilInstance, - starknet_l1_contract: StarknetL1Contract, + // Ethereum base layer coupled with an Anvil server instance, the server is dropped when the + // instance is dropped. + pub anvil_base_layer: AnvilBaseLayer, // The transactions that were streamed in the consensus proposals, used for asserting the right // transactions are batched. @@ -121,14 +121,13 @@ impl FlowTestSetup { .try_into() .unwrap(); - let (base_layer_config, base_layer_url) = - ethereum_base_layer_config_for_anvil(Some(available_ports.get_next_port())); - let (anvil, starknet_l1_contract) = - spawn_anvil_and_deploy_starknet_l1_contract(&base_layer_config, &base_layer_url).await; + let anvil_base_layer = AnvilBaseLayer::new(None).await; + let base_layer_url = anvil_base_layer.get_url().await.unwrap(); + let base_layer_config = anvil_base_layer.ethereum_base_layer.config.clone(); // Send some transactions to L1 so it has a history of blocks to scrape gas prices from. - let sender_address = anvil.addresses()[DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX]; - let receiver_address = anvil.addresses()[DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX + 1]; + let sender_address = ARBITRARY_ANVIL_L1_ACCOUNT_ADDRESS; + let receiver_address = OTHER_ARBITRARY_ANVIL_L1_ACCOUNT_ADDRESS; make_block_history_on_anvil( sender_address, receiver_address, @@ -180,7 +179,7 @@ impl FlowTestSetup { ) .await; - Self { sequencer_0, sequencer_1, l1_handle: anvil, starknet_l1_contract, accumulated_txs } + Self { sequencer_0, sequencer_1, anvil_base_layer, accumulated_txs } } pub fn chain_id(&self) -> &ChainId { @@ -196,9 +195,9 @@ impl FlowTestSetup { .chain_id } - pub async fn send_messages_to_l2(&self, l1_to_l2_messages_args: &[L1ToL2MessageArgs]) { - for l1_to_l2_message_args in l1_to_l2_messages_args { - self.starknet_l1_contract.send_message_to_l2(l1_to_l2_message_args).await; + pub async fn send_messages_to_l2(&self, l1_handlers: &[L1HandlerTransaction]) { + for l1_handler in l1_handlers { + self.anvil_base_layer.send_message_to_l2(l1_handler).await; } } } diff --git a/crates/apollo_integration_tests/src/integration_test_manager.rs b/crates/apollo_integration_tests/src/integration_test_manager.rs index f3a187e045c..479f3acbe11 100644 --- a/crates/apollo_integration_tests/src/integration_test_manager.rs +++ b/crates/apollo_integration_tests/src/integration_test_manager.rs @@ -1,16 +1,18 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::future::Future; use std::net::{Ipv4Addr, SocketAddr}; use std::panic; use std::path::PathBuf; use std::time::Duration; -use alloy::node_bindings::AnvilInstance; +use apollo_base_layer_tests::anvil_base_layer::AnvilBaseLayer; +use apollo_deployments::deployment_definitions::ComponentConfigInService; use apollo_http_server::test_utils::HttpTestClient; use apollo_http_server_config::config::HttpServerConfig; use apollo_infra_utils::dumping::serialize_to_file; use apollo_infra_utils::test_utils::{AvailablePortsGenerator, TestIdentifier}; use apollo_infra_utils::tracing::{CustomLogger, TraceLevel}; +use apollo_l1_endpoint_monitor::monitor::MIN_EXPECTED_BLOCK_NUMBER; use apollo_l1_gas_price_provider_config::config::{EthToStrkOracleConfig, L1GasPriceScraperConfig}; use apollo_monitoring_endpoint::test_utils::MonitoringClient; use apollo_monitoring_endpoint_config::config::MonitoringEndpointConfig; @@ -24,21 +26,14 @@ use apollo_test_utils::send_request; use blockifier::context::ChainInfo; use futures::future::join_all; use futures::TryFutureExt; +use indexmap::IndexMap; use mempool_test_utils::starknet_api_test_utils::{ contract_class, AccountId, MultiAccountTransactionGenerator, }; -use papyrus_base_layer::ethereum_base_layer_contract::{ - EthereumBaseLayerConfig, - StarknetL1Contract, -}; -use papyrus_base_layer::test_utils::{ - ethereum_base_layer_config_for_anvil, - make_block_history_on_anvil, - spawn_anvil_and_deploy_starknet_l1_contract, - DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX, -}; +use papyrus_base_layer::test_utils::anvil_mine_blocks; +use papyrus_base_layer::BaseLayerContract; use starknet_api::block::BlockNumber; use starknet_api::core::{ChainId, Nonce}; use starknet_api::execution_resources::GasAmount; @@ -48,7 +43,6 @@ use starknet_api::transaction::TransactionHash; use tokio::join; use tokio_util::task::AbortOnDropHandle; use tracing::{info, instrument}; -use url::Url; use crate::executable_setup::{ExecutableSetup, NodeExecutionId}; use crate::monitoring_utils::{ @@ -97,10 +91,13 @@ pub const MONITORING_PORT_ARG: &str = "monitoring-port"; const ALLOW_BOOTSTRAP_TXS: bool = false; pub struct NodeSetup { - executables: Vec, + // TODO(victork): remove indices. + executables: IndexMap, ExecutableSetup>, batcher_index: usize, + #[allow(dead_code)] http_server_index: usize, state_sync_index: usize, + #[allow(dead_code)] consensus_manager_index: usize, // Client for adding transactions to the sequencer node. @@ -115,7 +112,7 @@ pub struct NodeSetup { impl NodeSetup { pub fn new( - executables: Vec, + executables: IndexMap, ExecutableSetup>, batcher_index: usize, http_server_index: usize, state_sync_index: usize, @@ -153,19 +150,23 @@ impl NodeSetup { } pub fn batcher_monitoring_client(&self) -> &MonitoringClient { - &self.executables[self.batcher_index].monitoring_client + &self.get_batcher().monitoring_client } pub fn state_sync_monitoring_client(&self) -> &MonitoringClient { - &self.executables[self.state_sync_index].monitoring_client + &self.get_state_sync().monitoring_client } pub fn consensus_manager_monitoring_client(&self) -> &MonitoringClient { - &self.executables[self.consensus_manager_index].monitoring_client + &self.get_consensus_manager().monitoring_client } - pub fn get_executables(&self) -> &Vec { - &self.executables + pub fn get_executables(&self) -> impl ExactSizeIterator { + self.executables.values() + } + + fn get_executables_mut(&mut self) -> impl ExactSizeIterator { + self.executables.values_mut() } pub fn set_executable_config_path( @@ -173,8 +174,8 @@ impl NodeSetup { index: usize, new_path: PathBuf, ) -> Result<(), &'static str> { - if let Some(exec) = self.executables.get_mut(index) { - exec.node_config_path = new_path; + if let Some(exec) = self.executables.get_index_mut(index) { + exec.1.node_config_path = new_path; Ok(()) } else { panic!("Invalid executable index") @@ -183,28 +184,41 @@ impl NodeSetup { pub fn generate_simulator_ports_json(&self, path: &str) { let json_data = serde_json::json!({ - HTTP_PORT_ARG: self.executables[self.http_server_index].get_config().http_server_config.as_ref().expect("Should have http server config").port, - MONITORING_PORT_ARG: self.executables[self.batcher_index].get_config().monitoring_endpoint_config.as_ref().expect("Should have monitoring endpoint config").port + HTTP_PORT_ARG: self.get_http_server().get_config().http_server_config.as_ref().expect("Should have http server config").port, + MONITORING_PORT_ARG: self.get_batcher().get_config().monitoring_endpoint_config.as_ref().expect("Should have monitoring endpoint config").port }); - serialize_to_file(json_data, path); + serialize_to_file(&json_data, path); } - pub fn get_batcher_index(&self) -> usize { - self.batcher_index + fn get_executable_by_component(&self, component: ComponentConfigInService) -> &ExecutableSetup { + self.executables + .iter() + .find(|(components, _)| components.contains(&component)) + .map(|(_, executable)| executable) + .unwrap_or_else(|| { + panic!("Expected at least one executable with component {:?}", component) + }) } - pub fn get_http_server_index(&self) -> usize { - self.http_server_index + pub fn get_batcher(&self) -> &ExecutableSetup { + self.get_executable_by_component(ComponentConfigInService::Batcher) } - pub fn get_state_sync_index(&self) -> usize { - self.state_sync_index + pub fn get_http_server(&self) -> &ExecutableSetup { + self.get_executable_by_component(ComponentConfigInService::HttpServer) + } + + pub fn get_state_sync(&self) -> &ExecutableSetup { + self.get_executable_by_component(ComponentConfigInService::StateSync) + } + + pub fn get_consensus_manager(&self) -> &ExecutableSetup { + self.get_executable_by_component(ComponentConfigInService::Consensus) } pub fn run(self) -> RunningNode { let executable_handles = self .get_executables() - .iter() .map(|executable| { info!("Running {}.", executable.node_execution_id); spawn_run_node( @@ -217,7 +231,20 @@ impl NodeSetup { RunningNode { node_setup: self, executable_handles } } pub fn get_node_index(&self) -> Option { - self.executables.first().map(|executable| executable.node_execution_id.get_node_index()) + self.get_executables() + .next() + .map(|executable| executable.node_execution_id.get_node_index()) + } + + pub fn get_l1_gas_price_scraper_config(&self) -> L1GasPriceScraperConfig { + for executable_setup in self.get_executables() { + if let Some(l1_gas_price_scraper_config) = + &executable_setup.get_config().l1_gas_price_scraper_config + { + return l1_gas_price_scraper_config.clone(); + } + } + unreachable!("No executable with a set l1 gas price scraper config.") } } @@ -229,7 +256,7 @@ pub struct RunningNode { impl RunningNode { async fn await_alive(&self, interval: u64, max_attempts: usize) { self.propagate_executable_panic(); - let await_alive_tasks = self.node_setup.executables.iter().map(|executable| { + let await_alive_tasks = self.node_setup.get_executables().map(|executable| { let result = executable.monitoring_client.await_alive(interval, max_attempts); result.unwrap_or_else(|_| { panic!("Executable {:?} should be alive.", executable.node_execution_id) @@ -255,16 +282,16 @@ pub struct IntegrationTestManager { idle_nodes: HashMap, running_nodes: HashMap, tx_generator: MultiAccountTransactionGenerator, - // Handle for L1 server: the server is dropped when handle is dropped. - #[allow(dead_code)] - l1_handle: AnvilInstance, - starknet_l1_contract: StarknetL1Contract, + // Ethereum base layer coupled with an Anvil server instance, the server is dropped when the + // instance is dropped. + anvil_base_layer: AnvilBaseLayer, } impl IntegrationTestManager { pub async fn new( num_of_consolidated_nodes: usize, num_of_distributed_nodes: usize, + num_of_hybrid_nodes: usize, custom_paths: Option, test_unique_id: TestIdentifier, ) -> Self { @@ -274,79 +301,38 @@ impl IntegrationTestManager { &tx_generator, num_of_consolidated_nodes, num_of_distributed_nodes, + num_of_hybrid_nodes, custom_paths, test_unique_id, ) .await; - fn get_base_layer_config(sequencers_setup: &NodeSetup) -> (EthereumBaseLayerConfig, Url) { - // TODO(Tsabary): the pattern of iterating over executables to find the relevant config - // should be unified and improved, throughout. - for executable_setup in &sequencers_setup.executables { - // Must find both base layer config and url in the same executable, then return - // them. - if let Some(config) = &executable_setup.get_config().base_layer_config { - let base_layer_config = config; - if let Some(l1_monitor_config) = - &executable_setup.get_config().l1_endpoint_monitor_config - { - let base_layer_url = l1_monitor_config.ordered_l1_endpoint_urls[0].clone(); - return (base_layer_config.clone(), base_layer_url); - } - } - } - unreachable!("No executable with a set base layer config.") - } - let (base_layer_config, base_layer_url) = - get_base_layer_config(sequencers_setup.first().unwrap()); - - // TODO(Tsabary): these should be functions of `NodeSetup`. - fn get_l1_gas_price_scraper_config( - sequencers_setup: &NodeSetup, - ) -> L1GasPriceScraperConfig { - for executable_setup in &sequencers_setup.executables { - if let Some(l1_gas_price_scraper_config) = - &executable_setup.get_config().l1_gas_price_scraper_config - { - return l1_gas_price_scraper_config.clone(); - } - } - unreachable!("No executable with a set l1 gas price scraper config.") - } - let l1_gas_price_scraper_config = - get_l1_gas_price_scraper_config(sequencers_setup.first().unwrap()); + sequencers_setup.first().unwrap().get_l1_gas_price_scraper_config(); - let (anvil, starknet_l1_contract) = - spawn_anvil_and_deploy_starknet_l1_contract(&base_layer_config, &base_layer_url).await; + let anvil_base_layer = AnvilBaseLayer::new(Some(1)).await; // Send some transactions to L1 so it has a history of blocks to scrape gas prices from. - let num_blocks_needed_on_l1 = (l1_gas_price_scraper_config.number_of_blocks_for_mean - + l1_gas_price_scraper_config.finality) - .try_into() - .unwrap(); - let sender_address = anvil.addresses()[DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX]; - let receiver_address = anvil.addresses()[DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX + 1]; - - make_block_history_on_anvil( - sender_address, - receiver_address, - base_layer_config.clone(), - &base_layer_url, + let num_blocks_needed_on_l1 = l1_gas_price_scraper_config.number_of_blocks_for_mean + + l1_gas_price_scraper_config.finality; + + assert!( + num_blocks_needed_on_l1 <= MIN_EXPECTED_BLOCK_NUMBER, + "num_blocks_needed_on_l1 ({}) exceeds MIN_EXPECTED_BLOCK_NUMBER ({})", num_blocks_needed_on_l1, + MIN_EXPECTED_BLOCK_NUMBER + ); + + anvil_mine_blocks( + anvil_base_layer.ethereum_base_layer.config.clone(), + MIN_EXPECTED_BLOCK_NUMBER, + &anvil_base_layer.get_url().await.expect("Failed to get anvil url."), ) .await; let idle_nodes = create_map(sequencers_setup, |node| node.get_node_index()); let running_nodes = HashMap::new(); - Self { - node_indices, - idle_nodes, - running_nodes, - tx_generator, - l1_handle: anvil, - starknet_l1_contract, - } + Self { node_indices, idle_nodes, running_nodes, tx_generator, anvil_base_layer } } pub fn get_idle_nodes(&self) -> &HashMap { @@ -398,7 +384,7 @@ impl IntegrationTestManager { .idle_nodes .get_mut(&node_index) .unwrap_or_else(|| panic!("Node {node_index} does not exist in idle_nodes.")); - node_setup.executables.iter_mut().for_each(|executable| { + node_setup.get_executables_mut().for_each(|executable| { info!("Modifying {} config.", executable.node_execution_id); executable.modify_config(modify_config_fn); }); @@ -419,7 +405,7 @@ impl IntegrationTestManager { .idle_nodes .get_mut(&node_index) .unwrap_or_else(|| panic!("Node {node_index} does not exist in idle_nodes.")); - node_setup.executables.iter_mut().for_each(|executable| { + node_setup.get_executables_mut().for_each(|executable| { info!("Modifying {} config pointers.", executable.node_execution_id); executable.modify_config_pointers(modify_config_pointers_fn); }); @@ -445,7 +431,7 @@ impl IntegrationTestManager { "Waiting for batcher to reach block {expected_block_number} in sequencer {} \ executable {}.", running_node_setup.get_node_index().unwrap(), - running_node_setup.get_batcher_index(), + running_node_setup.batcher_index, )), ); @@ -457,7 +443,7 @@ impl IntegrationTestManager { "Waiting for state sync to reach block {expected_block_number} in sequencer \ {} executable {}.", running_node_setup.get_node_index().unwrap(), - running_node_setup.get_state_sync_index(), + running_node_setup.state_sync_index, )), ); @@ -549,7 +535,7 @@ impl IntegrationTestManager { .map(|node| &(node.node_setup)) .unwrap_or_else(|| self.idle_nodes.get(&0).expect("Node 0 doesn't exist")); - for executable_setup in &node_0_setup.executables { + for executable_setup in node_0_setup.get_executables() { if let Some(http_server_config) = &executable_setup.get_config().http_server_config { let localhost_url = format!("http://{}", Ipv4Addr::LOCALHOST); let monitoring_port = executable_setup @@ -627,7 +613,7 @@ impl IntegrationTestManager { .or_else(|| self.running_nodes.values().next().map(|node| &node.node_setup)) .expect("There should be at least one running or idle node"); - for executable_setup in &node_setup.executables { + for executable_setup in node_setup.get_executables() { if let Some(state_sync_config) = executable_setup.get_config().clone().state_sync_config { return SocketAddr::from(( @@ -704,7 +690,7 @@ impl IntegrationTestManager { let send_l1_handler_tx_fn = &mut |l1_handler_tx| { send_message_to_l2_and_calculate_tx_hash( l1_handler_tx, - &self.starknet_l1_contract, + &self.anvil_base_layer, &chain_id, ) }; @@ -729,14 +715,12 @@ impl IntegrationTestManager { self.perform_action_on_all_running_nodes(|sequencer_idx, running_node| { let node_setup = &running_node.node_setup; let batcher_monitoring_client = node_setup.batcher_monitoring_client(); - let batcher_index = node_setup.get_batcher_index(); let state_sync_monitoring_client = node_setup.state_sync_monitoring_client(); - let state_sync_index = node_setup.get_state_sync_index(); await_block( batcher_monitoring_client, - batcher_index, + node_setup.batcher_index, state_sync_monitoring_client, - state_sync_index, + node_setup.state_sync_index, expected_block_number, sequencer_idx, ) @@ -762,7 +746,7 @@ impl IntegrationTestManager { self.perform_action_on_all_running_nodes(|sequencer_idx, running_node| async move { let node_setup = &running_node.node_setup; let monitoring_client = node_setup.batcher_monitoring_client(); - let batcher_index = node_setup.get_batcher_index(); + let batcher_index = node_setup.batcher_index; let expected_height = expected_block_number.unchecked_next(); let logger = CustomLogger::new( @@ -815,7 +799,7 @@ impl IntegrationTestManager { .or_else(|| self.running_nodes.values().next().map(|node| &node.node_setup)) .expect("There should be at least one running or idle node"); - for executable_setup in &node_setup.executables { + for executable_setup in node_setup.get_executables() { if let Some(batcher_config) = &executable_setup.get_config().batcher_config { return batcher_config.block_builder_config.chain_info.chain_id.clone(); } @@ -856,23 +840,23 @@ async fn get_sequencer_setup_configs( // TODO(Tsabary/Nadin): instead of number of nodes, this should be a vector of deployments. num_of_consolidated_nodes: usize, num_of_distributed_nodes: usize, + num_of_hybrid_nodes: usize, custom_paths: Option, test_unique_id: TestIdentifier, ) -> (Vec, HashSet) { let mut available_ports_generator = AvailablePortsGenerator::new(test_unique_id.into()); - let mut node_component_configs = - Vec::with_capacity(num_of_consolidated_nodes + num_of_distributed_nodes); + let mut node_component_configs = Vec::with_capacity( + num_of_consolidated_nodes + num_of_distributed_nodes + num_of_hybrid_nodes, + ); for _ in 0..num_of_consolidated_nodes { node_component_configs.push(create_consolidated_component_configs()); } - // Testing the two various node configurations: distributed and hybrid. - // TODO(Tsabary): better handling of the number of each type. - for _ in 0..num_of_distributed_nodes / 2 { + for _ in 0..num_of_hybrid_nodes { node_component_configs .push(create_hybrid_component_configs(&mut available_ports_generator)); } - for _ in num_of_distributed_nodes / 2..num_of_distributed_nodes { + for _ in 0..num_of_distributed_nodes { node_component_configs .push(create_distributed_component_configs(&mut available_ports_generator)); } @@ -921,8 +905,8 @@ async fn get_sequencer_setup_configs( let mut base_layer_ports = available_ports_generator .next() .expect("Failed to get an AvailablePorts instance for base layer config"); - let (base_layer_config, base_layer_url) = - ethereum_base_layer_config_for_anvil(Some(base_layer_ports.get_next_port())); + let base_layer_config = AnvilBaseLayer::config(); + let base_layer_url = AnvilBaseLayer::url(); let mut nodes = Vec::new(); @@ -939,11 +923,11 @@ async fn get_sequencer_setup_configs( // Create nodes. for (node_index, node_component_config) in node_component_configs.into_iter().enumerate() { - let mut executables = Vec::new(); + let mut executables = IndexMap::new(); let batcher_index = node_component_config.get_batcher_index(); let http_server_index = node_component_config.get_http_server_index(); let state_sync_index = node_component_config.get_state_sync_index(); - let class_manager_index = node_component_config.get_class_manager_index(); + let _ = node_component_config.get_class_manager_index(); let consensus_manager_index = node_component_config.get_consensus_manager_index(); let mut consensus_manager_config = consensus_manager_configs.remove(0); @@ -961,16 +945,13 @@ async fn get_sequencer_setup_configs( let storage_setup = get_integration_test_storage( node_index, - batcher_index, - state_sync_index, - class_manager_index, custom_paths.clone(), accounts.to_vec(), &chain_info, ); // Per node, create the executables constituting it. - for (executable_index, executable_component_config) in + for (executable_index, (component_set, executable_component_config)) in node_component_config.into_iter().enumerate() { // Set a monitoring endpoint for each executable. @@ -1007,7 +988,8 @@ async fn get_sequencer_setup_configs( let exec_config_path = custom_paths.as_ref().and_then(|paths| paths.get_config_path(&node_execution_id)); - executables.push( + executables.insert( + component_set, ExecutableSetup::new(base_app_config, node_execution_id, exec_config_path).await, ); } diff --git a/crates/apollo_integration_tests/src/monitoring_utils.rs b/crates/apollo_integration_tests/src/monitoring_utils.rs index e0db4e77a07..a654d15fe48 100644 --- a/crates/apollo_integration_tests/src/monitoring_utils.rs +++ b/crates/apollo_integration_tests/src/monitoring_utils.rs @@ -2,6 +2,7 @@ use apollo_batcher::metrics::{REVERTED_TRANSACTIONS, STORAGE_HEIGHT}; use apollo_consensus::metrics::CONSENSUS_DECISIONS_REACHED_BY_CONSENSUS; use apollo_infra_utils::run_until::run_until; use apollo_infra_utils::tracing::{CustomLogger, TraceLevel}; +use apollo_metrics::metrics::MetricCommon; use apollo_monitoring_endpoint::test_utils::MonitoringClient; use apollo_state_sync_metrics::metrics::{ STATE_SYNC_BODY_MARKER, diff --git a/crates/apollo_integration_tests/src/node_component_configs.rs b/crates/apollo_integration_tests/src/node_component_configs.rs index 3a29d9c3007..c9d10be77f8 100644 --- a/crates/apollo_integration_tests/src/node_component_configs.rs +++ b/crates/apollo_integration_tests/src/node_component_configs.rs @@ -1,18 +1,17 @@ -use apollo_deployments::deployments::distributed::{ - DistributedNodeServiceName, - DISTRIBUTED_NODE_REQUIRED_PORTS_NUM, -}; -use apollo_deployments::deployments::hybrid::{ - HybridNodeServiceName, - HYBRID_NODE_REQUIRED_PORTS_NUM, -}; +use std::collections::BTreeSet; + +use apollo_deployments::deployment_definitions::ComponentConfigInService; +use apollo_deployments::deployments::distributed::DISTRIBUTED_NODE_REQUIRED_PORTS_NUM; +use apollo_deployments::deployments::hybrid::HYBRID_NODE_REQUIRED_PORTS_NUM; use apollo_deployments::service::{NodeService, NodeType}; use apollo_infra_utils::test_utils::AvailablePortsGenerator; use apollo_node_config::component_config::{set_urls_to_localhost, ComponentConfig}; +use indexmap::IndexMap; /// Holds the component configs for a set of sequencers, composing a single sequencer node. pub struct NodeComponentConfigs { - component_configs: Vec, + // TODO(victork): remove indices. + component_configs: IndexMap, ComponentConfig>, batcher_index: usize, http_server_index: usize, state_sync_index: usize, @@ -21,16 +20,51 @@ pub struct NodeComponentConfigs { } impl NodeComponentConfigs { - fn new( - component_configs: Vec, - batcher_index: usize, - http_server_index: usize, - state_sync_index: usize, - class_manager_index: usize, - consensus_manager_index: usize, - ) -> Self { + // TODO(victork): only pass node_type and ports, and create the map inside. + fn new(component_configs: IndexMap, node_type: NodeType) -> Self { + fn get_component_index( + component_configs: &IndexMap, + node_type: NodeType, + component_in_service: ComponentConfigInService, + ) -> usize { + component_configs + .get_index_of::( + node_type + .get_services_of_components(component_in_service) + .iter() + .next() + .as_ref() + .unwrap(), + ) + .unwrap() + } + + let batcher_index = + get_component_index(&component_configs, node_type, ComponentConfigInService::Batcher); + + let http_server_index = get_component_index( + &component_configs, + node_type, + ComponentConfigInService::HttpServer, + ); + + let state_sync_index = + get_component_index(&component_configs, node_type, ComponentConfigInService::StateSync); + + let class_manager_index = get_component_index( + &component_configs, + node_type, + ComponentConfigInService::ClassManager, + ); + + let consensus_manager_index = + get_component_index(&component_configs, node_type, ComponentConfigInService::Consensus); + Self { - component_configs, + component_configs: component_configs + .into_iter() + .map(|(service, config)| (service.get_components_in_service(), config)) + .collect(), batcher_index, http_server_index, state_sync_index, @@ -69,23 +103,19 @@ impl NodeComponentConfigs { } impl IntoIterator for NodeComponentConfigs { - type Item = ComponentConfig; + type Item = (BTreeSet, ComponentConfig); type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.component_configs.into_iter() + self.component_configs.into_iter().collect::>().into_iter() } } pub fn create_consolidated_component_configs() -> NodeComponentConfigs { // All components are in executable index 0. NodeComponentConfigs::new( - NodeType::Consolidated.get_component_configs(None).into_values().collect(), - 0, - 0, - 0, - 0, - 0, + NodeType::Consolidated.get_component_configs(None), + NodeType::Consolidated, ) } @@ -97,32 +127,11 @@ pub fn create_distributed_component_configs( .expect("Failed to get an AvailablePorts instance for distributed node configs"); let ports = available_ports.get_next_ports(DISTRIBUTED_NODE_REQUIRED_PORTS_NUM); - let services_component_config = NodeType::Distributed.get_component_configs(Some(ports)); - - let mut component_configs: Vec = - services_component_config.values().cloned().collect(); - set_urls_to_localhost(&mut component_configs); + let mut services_component_config = NodeType::Distributed.get_component_configs(Some(ports)); - // TODO(Tsabary): transition to using the map instead of a vector and indices. + set_urls_to_localhost(services_component_config.values_mut()); - NodeComponentConfigs::new( - component_configs, - services_component_config - .get_index_of::(&DistributedNodeServiceName::Batcher.into()) - .unwrap(), - services_component_config - .get_index_of::(&DistributedNodeServiceName::HttpServer.into()) - .unwrap(), - services_component_config - .get_index_of::(&DistributedNodeServiceName::StateSync.into()) - .unwrap(), - services_component_config - .get_index_of::(&DistributedNodeServiceName::ClassManager.into()) - .unwrap(), - services_component_config - .get_index_of::(&DistributedNodeServiceName::ConsensusManager.into()) - .unwrap(), - ) + NodeComponentConfigs::new(services_component_config, NodeType::Distributed) } pub fn create_hybrid_component_configs( @@ -133,30 +142,9 @@ pub fn create_hybrid_component_configs( .expect("Failed to get an AvailablePorts instance for distributed node configs"); let ports = available_ports.get_next_ports(HYBRID_NODE_REQUIRED_PORTS_NUM); - let services_component_config = NodeType::Hybrid.get_component_configs(Some(ports)); + let mut services_component_config = NodeType::Hybrid.get_component_configs(Some(ports)); - let mut component_configs: Vec = - services_component_config.values().cloned().collect(); - set_urls_to_localhost(&mut component_configs); + set_urls_to_localhost(services_component_config.values_mut()); - // TODO(Tsabary): transition to using the map instead of a vector and indices. - - NodeComponentConfigs::new( - component_configs, - services_component_config - .get_index_of::(&HybridNodeServiceName::Core.into()) - .unwrap(), - services_component_config - .get_index_of::(&HybridNodeServiceName::HttpServer.into()) - .unwrap(), - services_component_config - .get_index_of::(&HybridNodeServiceName::Core.into()) - .unwrap(), - services_component_config - .get_index_of::(&HybridNodeServiceName::Core.into()) - .unwrap(), - services_component_config - .get_index_of::(&HybridNodeServiceName::Core.into()) - .unwrap(), - ) + NodeComponentConfigs::new(services_component_config, NodeType::Hybrid) } diff --git a/crates/apollo_integration_tests/src/state_reader.rs b/crates/apollo_integration_tests/src/state_reader.rs index 3c7a102aac7..4fa0d4267a2 100644 --- a/crates/apollo_integration_tests/src/state_reader.rs +++ b/crates/apollo_integration_tests/src/state_reader.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use apollo_class_manager::class_storage::{ClassStorage, FsClassStorage}; use apollo_class_manager::test_utils::FsClassStorageBuilderForTesting; +use apollo_class_manager::{ClassStorage, FsClassStorage}; use apollo_class_manager_config::config::FsClassStorageConfig; use apollo_storage::body::BodyStorageWriter; use apollo_storage::class::ClassStorageWriter; @@ -32,6 +32,7 @@ use starknet_api::block::{ FeeType, GasPricePerToken, }; +use starknet_api::contract_class::compiled_class_hash::HashVersion; use starknet_api::contract_class::{ContractClass, SierraVersion}; use starknet_api::core::{ClassHash, ContractAddress, Nonce, SequencerContractAddress}; use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; @@ -278,10 +279,13 @@ fn prepare_state_diff( // Setup the common test contracts that are used by default in all test invokes. // TODO(batcher): this does nothing until we actually start excuting stuff in the batcher. - state_diff_builder.set_contracts(default_test_contracts).declare().deploy(); + state_diff_builder.set_contracts(default_test_contracts).declare(&HashVersion::V2).deploy(); // Declare and deploy and the ERC20 contract, so that transfers from it can be made. - state_diff_builder.set_contracts(std::slice::from_ref(erc20_contract)).declare().deploy(); + state_diff_builder + .set_contracts(std::slice::from_ref(erc20_contract)) + .declare(&HashVersion::V2) + .deploy(); // TODO(deploy_account_support): once we have batcher with execution, replace with: // ``` @@ -425,7 +429,7 @@ impl<'a> ThinStateDiffBuilder<'a> { self } - fn declare(&mut self) -> &mut Self { + fn declare(&mut self, hash_version: &HashVersion) -> &mut Self { for contract in self.contracts { match contract.cairo_version() { CairoVersion::Cairo0 => { @@ -434,7 +438,10 @@ impl<'a> ThinStateDiffBuilder<'a> { // todo(rdr): including both Cairo1 and Native versions for now. Temporal solution // to avoid compilation errors when using the "cairo_native" feature _ => { - self.declared_classes.insert(contract.class_hash(), Default::default()); + self.declared_classes.insert( + contract.class_hash(), + contract.contract.get_compiled_class_hash(hash_version), + ); } } } @@ -475,7 +482,10 @@ impl<'a> ThinStateDiffBuilder<'a> { &mut self, deployed_accounts_defined_in_the_test: &'a [Contract], ) { - self.set_contracts(deployed_accounts_defined_in_the_test).declare().deploy().fund(); + self.set_contracts(deployed_accounts_defined_in_the_test) + .declare(&HashVersion::V2) + .deploy() + .fund(); // Set nonces as 1 in the state so that subsequent invokes can pass validation. self.nonces = self @@ -489,7 +499,11 @@ impl<'a> ThinStateDiffBuilder<'a> { &mut self, undeployed_accounts_defined_in_the_test: &'a [Contract], ) { - self.set_contracts(undeployed_accounts_defined_in_the_test).declare().fund(); + // Imitate the behavior of a class that was declared before the migration + // with casm v1 (poseidon) to trigger migration in the integration tests. + self.set_contracts(undeployed_accounts_defined_in_the_test) + .declare(&HashVersion::V1) + .fund(); } fn build(self) -> ThinStateDiff { diff --git a/crates/apollo_integration_tests/src/storage.rs b/crates/apollo_integration_tests/src/storage.rs index b9c2d3068c6..5880d3b25f5 100644 --- a/crates/apollo_integration_tests/src/storage.rs +++ b/crates/apollo_integration_tests/src/storage.rs @@ -16,52 +16,40 @@ use crate::state_reader::{ #[derive(Debug)] pub struct StorageExecutablePaths { - batcher_path: PathBuf, - state_sync_path: PathBuf, - class_manager_path: PathBuf, + path: PathBuf, } impl StorageExecutablePaths { - pub fn new( - db_base: &Path, - node_index: usize, - batcher_index: usize, - state_sync_index: usize, - class_manager_index: usize, - ) -> Self { - let batcher_node_index = NodeExecutionId::new(node_index, batcher_index); - let state_sync_node_index = NodeExecutionId::new(node_index, state_sync_index); - let class_manager_node_index = NodeExecutionId::new(node_index, class_manager_index); + pub fn new(db_base: &Path, node_index: usize) -> Self { + let node_index = NodeExecutionId::new(node_index, 0); - let batcher_path = batcher_node_index.build_path(db_base); - let state_sync_path = state_sync_node_index.build_path(db_base); - let class_manager_path = class_manager_node_index.build_path(db_base); + let path = node_index.build_path(db_base); - Self { batcher_path, state_sync_path, class_manager_path } + Self { path } } pub fn get_batcher_exec_path(&self) -> &PathBuf { - &self.batcher_path + &self.path } pub fn get_state_sync_exec_path(&self) -> &PathBuf { - &self.state_sync_path + &self.path } pub fn get_class_manager_exec_path(&self) -> &PathBuf { - &self.class_manager_path + &self.path } pub fn get_batcher_path_with_db_suffix(&self) -> PathBuf { - self.batcher_path.join(BATCHER_DB_PATH_SUFFIX) + self.path.join(BATCHER_DB_PATH_SUFFIX) } pub fn get_state_sync_path_with_db_suffix(&self) -> PathBuf { - self.state_sync_path.join(STATE_SYNC_DB_PATH_SUFFIX) + self.path.join(STATE_SYNC_DB_PATH_SUFFIX) } pub fn get_class_manager_path_with_db_suffix(&self) -> PathBuf { - self.class_manager_path.join(CLASS_MANAGER_DB_PATH_SUFFIX) + self.path.join(CLASS_MANAGER_DB_PATH_SUFFIX) } } @@ -86,7 +74,11 @@ impl CustomPaths { } pub fn get_config_path(&self, node_execution_id: &NodeExecutionId) -> Option { - self.config_base.as_ref().map(|p| node_execution_id.build_path(p)) + self.config_base.as_ref().map(|p| { + node_execution_id + .build_path(p) + .join(format!("executable_{}", node_execution_id.get_executable_index())) + }) } pub fn get_data_prefix_path(&self) -> Option<&PathBuf> { @@ -96,23 +88,12 @@ impl CustomPaths { pub fn get_integration_test_storage( node_index: usize, - batcher_index: usize, - state_sync_index: usize, - class_manager_index: usize, custom_paths: Option, accounts: Vec, chain_info: &ChainInfo, ) -> StorageTestSetup { let storage_exec_paths = custom_paths.as_ref().and_then(|paths| { - paths.get_db_base().map(|db_base| { - StorageExecutablePaths::new( - db_base, - node_index, - batcher_index, - state_sync_index, - class_manager_index, - ) - }) + paths.get_db_base().map(|db_base| StorageExecutablePaths::new(db_base, node_index)) }); let StorageTestSetup { mut storage_config, storage_handles } = @@ -121,13 +102,7 @@ pub fn get_integration_test_storage( // Allow overriding the path with a custom prefix for Docker mode in system tests. if let Some(paths) = custom_paths { if let Some(prefix) = paths.get_data_prefix_path() { - let custom_storage_exec_paths = StorageExecutablePaths::new( - prefix, - node_index, - batcher_index, - state_sync_index, - class_manager_index, - ); + let custom_storage_exec_paths = StorageExecutablePaths::new(prefix, node_index); storage_config.batcher_storage_config.db_config.path_prefix = custom_storage_exec_paths.get_batcher_exec_path().join(BATCHER_DB_PATH_SUFFIX); storage_config.state_sync_storage_config.db_config.path_prefix = diff --git a/crates/apollo_integration_tests/src/utils.rs b/crates/apollo_integration_tests/src/utils.rs index 921180183e1..4eb06ae4da6 100644 --- a/crates/apollo_integration_tests/src/utils.rs +++ b/crates/apollo_integration_tests/src/utils.rs @@ -2,6 +2,7 @@ use std::future::Future; use std::net::SocketAddr; use std::time::Duration; +use apollo_base_layer_tests::anvil_base_layer::AnvilBaseLayer; use apollo_batcher::pre_confirmed_cende_client::RECORDER_WRITE_PRE_CONFIRMED_BLOCK_PATH; use apollo_batcher_config::config::{BatcherConfig, BlockBuilderConfig}; use apollo_class_manager_config::config::{ @@ -12,7 +13,12 @@ use apollo_class_manager_config::config::{ }; use apollo_config::converters::UrlAndHeaders; use apollo_config_manager_config::config::ConfigManagerConfig; -use apollo_consensus_config::config::{ConsensusConfig, ConsensusStaticConfig, TimeoutsConfig}; +use apollo_consensus_config::config::{ + ConsensusConfig, + ConsensusDynamicConfig, + ConsensusStaticConfig, + TimeoutsConfig, +}; use apollo_consensus_config::ValidatorId; use apollo_consensus_manager_config::config::ConsensusManagerConfig; use apollo_consensus_orchestrator::cende::RECORDER_WRITE_BLOB_PATH; @@ -60,11 +66,7 @@ use blockifier::context::ChainInfo; use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1}; use blockifier_test_utils::contracts::FeatureContract; use mempool_test_utils::starknet_api_test_utils::{AccountId, MultiAccountTransactionGenerator}; -use papyrus_base_layer::ethereum_base_layer_contract::{ - EthereumBaseLayerConfig, - L1ToL2MessageArgs, - StarknetL1Contract, -}; +use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerConfig; use serde::Deserialize; use serde_json::{json, to_value}; use starknet_api::block::BlockNumber; @@ -72,7 +74,7 @@ use starknet_api::core::{ChainId, ContractAddress}; use starknet_api::execution_resources::GasAmount; use starknet_api::rpc_transaction::RpcTransaction; use starknet_api::transaction::fields::ContractAddressSalt; -use starknet_api::transaction::{TransactionHash, TransactionHasher}; +use starknet_api::transaction::{L1HandlerTransaction, TransactionHash, TransactionHasher}; use starknet_types_core::felt::Felt; use tokio::task::JoinHandle; use tracing::{debug, info, Instrument}; @@ -91,7 +93,7 @@ pub const N_TXS_IN_FIRST_BLOCK: usize = 2; pub type CreateRpcTxsFn = fn(&mut MultiAccountTransactionGenerator) -> Vec; pub type CreateL1ToL2MessagesArgsFn = - fn(&mut MultiAccountTransactionGenerator) -> Vec; + fn(&mut MultiAccountTransactionGenerator) -> Vec; pub type TestTxHashesFn = fn(&[TransactionHash]) -> Vec; pub trait TestScenario { @@ -99,7 +101,7 @@ pub trait TestScenario { &self, tx_generator: &mut MultiAccountTransactionGenerator, account_id: AccountId, - ) -> (Vec, Vec); + ) -> (Vec, Vec); fn n_txs(&self) -> usize; } @@ -114,10 +116,11 @@ impl TestScenario for ConsensusTxs { &self, tx_generator: &mut MultiAccountTransactionGenerator, account_id: AccountId, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { + const SHOULD_REVERT: bool = false; ( create_invoke_txs(tx_generator, account_id, self.n_invoke_txs), - create_l1_to_l2_messages_args(tx_generator, self.n_l1_handler_txs), + create_l1_to_l2_messages_args(tx_generator, self.n_l1_handler_txs, SHOULD_REVERT), ) } @@ -133,7 +136,7 @@ impl TestScenario for DeclareTx { &self, tx_generator: &mut MultiAccountTransactionGenerator, account_id: AccountId, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { let declare_tx = tx_generator.account_with_id_mut(account_id).generate_declare_of_contract_class(); (vec![declare_tx], vec![]) @@ -151,7 +154,7 @@ impl TestScenario for DeployAndInvokeTxs { &self, tx_generator: &mut MultiAccountTransactionGenerator, account_id: AccountId, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { let txs = create_deploy_account_tx_and_invoke_tx(tx_generator, account_id); assert_eq!( txs.len(), @@ -199,7 +202,7 @@ pub fn create_node_config( let l1_scraper_config = L1ScraperConfig { chain_id: chain_info.chain_id.clone(), startup_rewind_time_seconds: Duration::from_secs(0), - polling_interval_seconds: Duration::from_secs(0), + polling_interval_seconds: Duration::from_secs(1), ..Default::default() }; let l1_provider_config = L1ProviderConfig { @@ -352,13 +355,15 @@ pub(crate) fn create_consensus_manager_configs_from_network_configs( network_config, immediate_active_height: BlockNumber(1), consensus_manager_config: ConsensusConfig { + dynamic_config: ConsensusDynamicConfig { + timeouts: timeouts.clone(), + ..Default::default() + }, static_config: ConsensusStaticConfig { // TODO(Matan, Dan): Set the right amount startup_delay: Duration::from_secs(15), - timeouts: timeouts.clone(), ..Default::default() }, - ..Default::default() }, context_config: ContextConfig { num_validators, @@ -525,20 +530,18 @@ pub fn create_invoke_txs( pub fn create_l1_to_l2_messages_args( tx_generator: &mut MultiAccountTransactionGenerator, n_txs: usize, -) -> Vec { - (0..n_txs).map(|_| tx_generator.create_l1_to_l2_message_args()).collect() + should_revert: bool, +) -> Vec { + (0..n_txs).map(|_| tx_generator.create_l1_to_l2_message_args(should_revert)).collect() } pub async fn send_message_to_l2_and_calculate_tx_hash( - send_message_to_l2_args: L1ToL2MessageArgs, - starknet_l1_contract: &StarknetL1Contract, + l1_handler: L1HandlerTransaction, + anvil_base_layer: &AnvilBaseLayer, chain_id: &ChainId, ) -> TransactionHash { - starknet_l1_contract.send_message_to_l2(&send_message_to_l2_args).await; - send_message_to_l2_args - .tx - .calculate_transaction_hash(chain_id, &send_message_to_l2_args.tx.version) - .unwrap() + anvil_base_layer.send_message_to_l2(&l1_handler).await; + l1_handler.calculate_transaction_hash(chain_id, &l1_handler.version).unwrap() } async fn send_rpc_txs<'a, Fut>( @@ -562,7 +565,7 @@ where pub async fn run_test_scenario<'a, Fut>( tx_generator: &mut MultiAccountTransactionGenerator, create_rpc_txs_fn: CreateRpcTxsFn, - l1_to_l2_message_args: Vec, + l1_handlers: Vec, send_rpc_tx_fn: &'a mut dyn Fn(RpcTransaction) -> Fut, test_tx_hashes_fn: TestTxHashesFn, chain_id: &ChainId, @@ -570,9 +573,11 @@ pub async fn run_test_scenario<'a, Fut>( where Fut: Future + 'a, { - let mut tx_hashes: Vec = l1_to_l2_message_args + let mut tx_hashes: Vec = l1_handlers .iter() - .map(|args| args.tx.calculate_transaction_hash(chain_id, &args.tx.version).unwrap()) + .map(|l1_handler| { + l1_handler.calculate_transaction_hash(chain_id, &l1_handler.version).unwrap() + }) .collect(); let rpc_txs = create_rpc_txs_fn(tx_generator); @@ -586,7 +591,7 @@ pub async fn send_consensus_txs<'a, 'b, FutA, FutB>( account_id: AccountId, test_scenario: &impl TestScenario, send_rpc_tx_fn: &'a mut dyn Fn(RpcTransaction) -> FutA, - send_l1_handler_tx_fn: &'b mut dyn Fn(L1ToL2MessageArgs) -> FutB, + send_l1_handler_tx_fn: &'b mut dyn Fn(L1HandlerTransaction) -> FutB, ) -> Vec where FutA: Future + 'a, diff --git a/crates/apollo_integration_tests/tests/bootstrap_declare.rs b/crates/apollo_integration_tests/tests/bootstrap_declare.rs index f0bf0caa12e..a65ce104c7f 100644 --- a/crates/apollo_integration_tests/tests/bootstrap_declare.rs +++ b/crates/apollo_integration_tests/tests/bootstrap_declare.rs @@ -2,7 +2,7 @@ use apollo_infra_utils::test_utils::TestIdentifier; use mempool_test_utils::starknet_api_test_utils::generate_bootstrap_declare; use starknet_api::execution_resources::GasAmount; -use crate::common::{end_to_end_flow, test_single_tx, TestScenario}; +use crate::common::{end_to_end_flow, test_single_tx, EndToEndFlowArgs, TestScenario}; mod common; @@ -17,14 +17,16 @@ fn create_bootstrap_declare_scenario() -> Vec { /// Bootstrap declare txs are unique: they are sent from a special address and do not increment its /// nonce. As a result, they are not removed from the mempool upon successful execution, and will /// only be removed after being rejected during a subsequent attempt. -#[tokio::test] +/// The test uses 3 threads: 1 for the test's main thread and 2 for the sequencers. +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn bootstrap_declare() { end_to_end_flow( - TestIdentifier::EndToEndFlowTestBootstrapDeclare, - create_bootstrap_declare_scenario(), - GasAmount(29000000), - false, - true, + EndToEndFlowArgs::new( + TestIdentifier::EndToEndFlowTestBootstrapDeclare, + create_bootstrap_declare_scenario(), + GasAmount(29000000), + ) + .allow_bootstrap_txs(), ) .await } diff --git a/crates/apollo_integration_tests/tests/common/mod.rs b/crates/apollo_integration_tests/tests/common/mod.rs index eac0bf3fa4b..b6243edbb64 100644 --- a/crates/apollo_integration_tests/tests/common/mod.rs +++ b/crates/apollo_integration_tests/tests/common/mod.rs @@ -26,16 +26,56 @@ use starknet_api::execution_resources::GasAmount; use starknet_api::transaction::TransactionHash; use tracing::info; +pub struct EndToEndFlowArgs { + pub test_identifier: TestIdentifier, + pub test_blocks_scenarios: Vec, + pub block_max_capacity_gas: GasAmount, // Used to max both sierra and proving gas. + pub expecting_full_blocks: bool, + pub expecting_reverted_transactions: bool, + pub allow_bootstrap_txs: bool, +} + +impl EndToEndFlowArgs { + pub fn new( + test_identifier: TestIdentifier, + test_blocks_scenarios: Vec, + block_max_capacity_gas: GasAmount, + ) -> Self { + Self { + test_identifier, + test_blocks_scenarios, + block_max_capacity_gas, + expecting_full_blocks: false, + expecting_reverted_transactions: false, + allow_bootstrap_txs: false, + } + } + + pub fn expecting_full_blocks(self) -> Self { + Self { expecting_full_blocks: true, ..self } + } + + pub fn expecting_reverted_transactions(self) -> Self { + Self { expecting_reverted_transactions: true, ..self } + } + + pub fn allow_bootstrap_txs(self) -> Self { + Self { allow_bootstrap_txs: true, ..self } + } +} + // Note: run integration/flow tests from separate files in `tests/`, which helps cargo ensure // isolation (prevent cross-contamination of services/resources) and that these tests won't be // parallelized (which won't work with fixed ports). -pub async fn end_to_end_flow( - test_identifier: TestIdentifier, - test_blocks_scenarios: Vec, - block_max_capacity_gas: GasAmount, // Used to max both sierra and proving gas. - expecting_full_blocks: bool, - allow_bootstrap_txs: bool, -) { +pub async fn end_to_end_flow(args: EndToEndFlowArgs) { + let EndToEndFlowArgs { + test_identifier, + test_blocks_scenarios, + block_max_capacity_gas, + expecting_full_blocks, + expecting_reverted_transactions, + allow_bootstrap_txs, + } = args; configure_tracing().await; let mut tx_generator = create_flow_test_tx_generator(); @@ -81,14 +121,14 @@ pub async fn end_to_end_flow( info!("Starting scenario {i}."); // Create and send transactions. // TODO(Arni): move send messages to l2 into [run_test_scenario]. - let l1_to_l2_messages_args = create_l1_to_l2_messages_args_fn(&mut tx_generator); - mock_running_system.send_messages_to_l2(&l1_to_l2_messages_args).await; + let l1_handlers = create_l1_to_l2_messages_args_fn(&mut tx_generator); + mock_running_system.send_messages_to_l2(&l1_handlers).await; // Run the test scenario and get the expected batched tx hashes of the current scenario. let expected_batched_tx_hashes = run_test_scenario( &mut tx_generator, create_rpc_txs_fn, - l1_to_l2_messages_args, + l1_handlers, &mut send_rpc_tx_fn, test_tx_hashes_fn, &chain_id, @@ -127,7 +167,10 @@ pub async fn end_to_end_flow( } assert_full_blocks_flow(&global_recorder_handle, expecting_full_blocks); - assert_no_reverted_transactions_flow(&global_recorder_handle); + assert_on_number_of_reverted_transactions_flow( + &global_recorder_handle, + expecting_reverted_transactions, + ); } pub struct TestScenario { @@ -153,17 +196,39 @@ fn assert_full_blocks_flow(recorder_handle: &PrometheusHandle, expecting_full_bl ) .unwrap(); if expecting_full_blocks { - assert!(full_blocks_metric > 0); + assert!( + full_blocks_metric > 0, + "Expected full blocks, but found {full_blocks_metric} full blocks." + ); } else { - assert_eq!(full_blocks_metric, 0); + assert_eq!( + full_blocks_metric, 0, + "Expected no full blocks, but found {full_blocks_metric} full blocks." + ); } } -fn assert_no_reverted_transactions_flow(recorder_handle: &PrometheusHandle) { +fn assert_on_number_of_reverted_transactions_flow( + recorder_handle: &PrometheusHandle, + expecting_reverted_transactions: bool, +) { let metrics = recorder_handle.render(); let reverted_transactions_metric = REVERTED_TRANSACTIONS.parse_numeric_metric::(&metrics).unwrap(); - assert_eq!(reverted_transactions_metric, 0); + + if expecting_reverted_transactions { + assert!( + reverted_transactions_metric > 0, + "Expected reverted transactions, but found {reverted_transactions_metric} reverted \ + transactions." + ); + } else { + assert_eq!( + reverted_transactions_metric, 0, + "Expected no reverted transactions, but found {reverted_transactions_metric} reverted \ + transactions." + ); + } } async fn wait_for_sequencer_node(sequencer: &FlowSequencerSetup) { diff --git a/crates/apollo_integration_tests/tests/end_to_end_flow_test.rs b/crates/apollo_integration_tests/tests/end_to_end_flow_test.rs index 630c7a8493a..826cb113cc7 100644 --- a/crates/apollo_integration_tests/tests/end_to_end_flow_test.rs +++ b/crates/apollo_integration_tests/tests/end_to_end_flow_test.rs @@ -8,27 +8,26 @@ use apollo_integration_tests::utils::{ }; use blockifier::bouncer::BouncerWeights; use mempool_test_utils::starknet_api_test_utils::MultiAccountTransactionGenerator; -use papyrus_base_layer::ethereum_base_layer_contract::L1ToL2MessageArgs; use starknet_api::rpc_transaction::RpcTransaction; -use starknet_api::transaction::TransactionHash; +use starknet_api::transaction::{L1HandlerTransaction, TransactionHash}; -use crate::common::{end_to_end_flow, test_single_tx, TestScenario}; +use crate::common::{end_to_end_flow, test_single_tx, EndToEndFlowArgs, TestScenario}; mod common; -#[tokio::test] +// TODO(Meshi): Fail the test if no class have migrated. +/// Number of threads is 3 = Num of sequencer + 1 for the test thread. +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn test_end_to_end_flow() { - end_to_end_flow( + end_to_end_flow(EndToEndFlowArgs::new( TestIdentifier::EndToEndFlowTest, create_test_scenarios(), BouncerWeights::default().proving_gas, - false, - false, - ) + )) .await } -pub fn create_test_scenarios() -> Vec { +fn create_test_scenarios() -> Vec { vec![ // This block should be the first to be tested, as the addition of L1 handler transaction // does not work smoothly with the current architecture of the test. @@ -63,9 +62,10 @@ pub fn create_test_scenarios() -> Vec { fn create_l1_to_l2_message_args( tx_generator: &mut MultiAccountTransactionGenerator, -) -> Vec { +) -> Vec { const N_TXS: usize = 1; - create_l1_to_l2_messages_args(tx_generator, N_TXS) + const SHOULD_REVERT: bool = false; + create_l1_to_l2_messages_args(tx_generator, N_TXS, SHOULD_REVERT) } fn create_multiple_account_txs( diff --git a/crates/apollo_integration_tests/tests/events_from_other_contracts.rs b/crates/apollo_integration_tests/tests/events_from_other_contracts.rs new file mode 100644 index 00000000000..469b3ca5ee1 --- /dev/null +++ b/crates/apollo_integration_tests/tests/events_from_other_contracts.rs @@ -0,0 +1,71 @@ +use apollo_base_layer_tests::anvil_base_layer::{send_message_to_l2, AnvilBaseLayer}; +use assert_matches::assert_matches; +use papyrus_base_layer::constants::{EventIdentifier, LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER}; +use papyrus_base_layer::ethereum_base_layer_contract::Starknet; +use papyrus_base_layer::{BaseLayerContract, L1Event}; +use pretty_assertions::assert_eq; +use starknet_api::core::EntryPointSelector; +use starknet_api::transaction::L1HandlerTransaction; +use starknet_api::{calldata, contract_address, felt}; + +// Ensure that the base layer instance filters out events from other deployments of the core +// contract. +#[tokio::test] +async fn events_from_other_contract() { + const EVENT_IDENTIFIERS: &[EventIdentifier] = &[LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER]; + + let anvil_base_layer = AnvilBaseLayer::new(None).await; + // Anvil base layer already auto-deployed a starknet contract. + let this_contract = &anvil_base_layer.ethereum_base_layer.contract; + + // Setup. + + // Deploy another instance of the contract to the same anvil instance. + let other_contract = Starknet::deploy(this_contract.provider().clone()).await.unwrap(); + assert_ne!( + this_contract.address(), + other_contract.address(), + "The two contracts should be different." + ); + + let this_l1_handler = L1HandlerTransaction { + contract_address: contract_address!("0x12"), + entry_point_selector: EntryPointSelector(felt!("0x34")), + calldata: calldata!( + AnvilBaseLayer::DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS, + felt!("0x1"), + felt!("0x2") + ), + ..Default::default() + }; + let this_receipt = send_message_to_l2(this_contract, &this_l1_handler.clone()).await; + assert!(this_receipt.status()); + let this_block_number = this_receipt.block_number.unwrap(); + + let other_l1_handler = L1HandlerTransaction { + contract_address: contract_address!("0x56"), + entry_point_selector: EntryPointSelector(felt!("0x78")), + calldata: calldata!( + AnvilBaseLayer::DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS, + felt!("0x1"), + felt!("0x2") + ), + ..Default::default() + }; + let other_receipt = send_message_to_l2(&other_contract, &other_l1_handler.clone()).await; + assert!(other_receipt.status()); + let other_block_number = other_receipt.block_number.unwrap(); + + let min_block_number = this_block_number.min(other_block_number).saturating_sub(1); + let max_block_number = this_block_number.max(other_block_number).saturating_add(1); + + // Test the events. + let mut events = anvil_base_layer + .ethereum_base_layer + .events(min_block_number..=max_block_number, EVENT_IDENTIFIERS) + .await + .unwrap(); + + assert_eq!(events.len(), 1, "Expected only events from this contract."); + assert_matches!(events.remove(0), L1Event::LogMessageToL2 { tx, .. } if tx == this_l1_handler); +} diff --git a/crates/apollo_l1_provider/tests/scraper_end_to_end.rs b/crates/apollo_integration_tests/tests/l1_events_scraper_end_to_end.rs similarity index 72% rename from crates/apollo_l1_provider/tests/scraper_end_to_end.rs rename to crates/apollo_integration_tests/tests/l1_events_scraper_end_to_end.rs index eb66ba42f04..26797aaef84 100644 --- a/crates/apollo_l1_provider/tests/scraper_end_to_end.rs +++ b/crates/apollo_integration_tests/tests/l1_events_scraper_end_to_end.rs @@ -2,17 +2,14 @@ use std::sync::Arc; use std::time::Duration; use alloy::primitives::U256; +use apollo_base_layer_tests::anvil_base_layer::AnvilBaseLayer; use apollo_l1_provider::event_identifiers_to_track; use apollo_l1_provider::l1_scraper::{fetch_start_block, L1Scraper}; use apollo_l1_provider_types::{Event, MockL1ProviderClient}; use apollo_l1_scraper_config::config::L1ScraperConfig; +use mockall::predicate::eq; use mockall::Sequence; -use papyrus_base_layer::ethereum_base_layer_contract::{EthereumBaseLayerContract, Starknet}; -use papyrus_base_layer::test_utils::{ - anvil_instance_from_url, - ethereum_base_layer_config_for_anvil, - DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS, -}; +use papyrus_base_layer::test_utils::DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS; use papyrus_base_layer::BaseLayerContract; use starknet_api::block::BlockTimestamp; use starknet_api::contract_address; @@ -22,49 +19,45 @@ use starknet_api::hash::StarkHash; use starknet_api::transaction::fields::{Calldata, Fee}; use starknet_api::transaction::{L1HandlerTransaction, TransactionHasher, TransactionVersion}; -pub fn in_ci() -> bool { - std::env::var("CI").is_ok() -} - #[tokio::test] async fn scraper_end_to_end() { - // if !in_ci() { - // return; - // } - // Setup. - let (base_layer_config, base_layer_url) = ethereum_base_layer_config_for_anvil(None); - let _anvil_server_guard = anvil_instance_from_url(&base_layer_url); + let base_layer = AnvilBaseLayer::new(None).await; + let contract = &base_layer.ethereum_base_layer.contract; let mut l1_provider_client = MockL1ProviderClient::default(); - let base_layer = EthereumBaseLayerContract::new(base_layer_config, base_layer_url); - - // Deploy a fresh Starknet contract on Anvil from the bytecode in the JSON file. - Starknet::deploy(base_layer.contract.provider().clone()).await.unwrap(); // Send messages from L1 to L2. let l2_contract_address = "0x12"; let l2_entry_point = "0x34"; - let message_to_l2_0 = base_layer.contract.sendMessageToL2( - l2_contract_address.parse().unwrap(), - l2_entry_point.parse().unwrap(), - vec![U256::from(1_u8), U256::from(2_u8)], - ); - let message_to_l2_1 = base_layer.contract.sendMessageToL2( - l2_contract_address.parse().unwrap(), - l2_entry_point.parse().unwrap(), - vec![U256::from(3_u8), U256::from(4_u8)], - ); + let fee = 1_u8; + let message_to_l2_0 = contract + .sendMessageToL2( + l2_contract_address.parse().unwrap(), + l2_entry_point.parse().unwrap(), + vec![U256::from(1_u8), U256::from(2_u8)], + ) + .value(U256::from(fee)); + let message_to_l2_1 = contract + .sendMessageToL2( + l2_contract_address.parse().unwrap(), + l2_entry_point.parse().unwrap(), + vec![U256::from(3_u8), U256::from(4_u8)], + ) + .value(U256::from(fee)); let nonce_of_message_to_l2_0 = U256::from(0_u8); - let request_cancel_message_0 = base_layer.contract.startL1ToL2MessageCancellation( - l2_contract_address.parse().unwrap(), - l2_entry_point.parse().unwrap(), - vec![U256::from(1_u8), U256::from(2_u8)], - nonce_of_message_to_l2_0, - ); + let request_cancel_message_0 = contract + .startL1ToL2MessageCancellation( + l2_contract_address.parse().unwrap(), + l2_entry_point.parse().unwrap(), + vec![U256::from(1_u8), U256::from(2_u8)], + nonce_of_message_to_l2_0, + ) + .from(DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS.to_hex_string().parse().unwrap()); // Send the transactions to Anvil, and record the timestamps of the blocks they are included in. let mut l1_handler_timestamps: Vec = Vec::with_capacity(2); for msg in &[message_to_l2_0, message_to_l2_1] { + msg.call().await.unwrap(); // Query for errors. let receipt = msg.send().await.unwrap().get_receipt().await.unwrap(); l1_handler_timestamps.push( base_layer @@ -76,6 +69,7 @@ async fn scraper_end_to_end() { ); } + request_cancel_message_0.call().await.unwrap(); // Query for errors; let cancel_receipt = request_cancel_message_0.send().await.unwrap().get_receipt().await.unwrap(); let cancel_timestamp = base_layer @@ -102,7 +96,7 @@ async fn scraper_end_to_end() { let expected_executable_l1_handler_0 = ExecutableL1HandlerTransaction { tx_hash: tx_hash_first_tx, tx: expected_l1_handler_0, - paid_fee_on_l1: Fee(0), + paid_fee_on_l1: Fee(fee.into()), }; let first_expected_log = Event::L1HandlerTransaction { l1_handler_tx: expected_executable_l1_handler_0.clone(), @@ -135,23 +129,13 @@ async fn scraper_end_to_end() { cancellation_request_timestamp: cancel_timestamp, }; - let expected_events = vec![first_expected_log, second_expected_log, expected_cancel_message]; - let mut sequence = Sequence::new(); // Expect first call to return all the events defined further down. l1_provider_client .expect_add_events() .once() .in_sequence(&mut sequence) - .withf(move |events| { - if events.len() != expected_events.len() { - return false; - } - for (event, expected_event) in events.iter().zip(expected_events.iter()) { - event.assert_event_almost_eq(expected_event); - } - true - }) + .with(eq(vec![first_expected_log, second_expected_log, expected_cancel_message])) .returning(|_| Ok(())); // Expect second call to return nothing, no events left to scrape. @@ -166,7 +150,7 @@ async fn scraper_end_to_end() { let mut scraper = L1Scraper::new( l1_scraper_config, Arc::new(l1_provider_client), - base_layer.clone(), + base_layer.ethereum_base_layer.clone(), event_identifiers_to_track(), l1_start_block, ) diff --git a/crates/apollo_integration_tests/tests/l1_handler_flow_test.rs b/crates/apollo_integration_tests/tests/l1_handler_flow_test.rs new file mode 100644 index 00000000000..c0251d2a70d --- /dev/null +++ b/crates/apollo_integration_tests/tests/l1_handler_flow_test.rs @@ -0,0 +1,39 @@ +use apollo_infra_utils::test_utils::TestIdentifier; +use apollo_integration_tests::utils::create_l1_to_l2_messages_args; +use blockifier::bouncer::BouncerWeights; +use mempool_test_utils::starknet_api_test_utils::MultiAccountTransactionGenerator; +use starknet_api::transaction::L1HandlerTransaction; + +use crate::common::{end_to_end_flow, test_single_tx, EndToEndFlowArgs, TestScenario}; + +mod common; + +/// Number of threads is 3 = Num of sequencer + 1 for the test thread. +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] +async fn reverted_l1_handler_tx_flow() { + end_to_end_flow( + EndToEndFlowArgs::new( + TestIdentifier::RevertedL1HandlerTx, + create_test_scenarios(), + BouncerWeights::default().proving_gas, + ) + .expecting_reverted_transactions(), + ) + .await +} + +fn create_test_scenarios() -> Vec { + vec![TestScenario { + create_rpc_txs_fn: |_| vec![], + create_l1_to_l2_messages_args_fn: create_l1_to_l2_reverted_message_args, + test_tx_hashes_fn: test_single_tx, + }] +} + +fn create_l1_to_l2_reverted_message_args( + tx_generator: &mut MultiAccountTransactionGenerator, +) -> Vec { + const N_TXS: usize = 1; + const SHOULD_REVERT: bool = true; + create_l1_to_l2_messages_args(tx_generator, N_TXS, SHOULD_REVERT) +} diff --git a/crates/apollo_integration_tests/tests/latest_proved_block_ethereum.rs b/crates/apollo_integration_tests/tests/latest_proved_block_ethereum.rs new file mode 100644 index 00000000000..46294fee714 --- /dev/null +++ b/crates/apollo_integration_tests/tests/latest_proved_block_ethereum.rs @@ -0,0 +1,163 @@ +use alloy::providers::Provider; +use apollo_base_layer_tests::anvil_base_layer::{AnvilBaseLayer, MockedStateUpdate}; +use assert_matches::assert_matches; +use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerError; +use papyrus_base_layer::BaseLayerContract; +use pretty_assertions::assert_eq; +use starknet_api::block::{BlockHash, BlockHashAndNumber, BlockNumber}; +use starknet_api::hash::StarkHash; + +#[tokio::test] +async fn latest_proved_block_ethereum() { + // State update constants. + const FIRST_ETHEREUM_BLOCK: u64 = 6; + const FIRST_STARKNET_BLOCK_NUMBER: u64 = 2; + const FIRST_STARKNET_BLOCK_HASH: u64 = 0x2; + + const SECOND_ETHEREUM_BLOCK: u64 = 16; + const SECOND_STARKNET_BLOCK_NUMBER: u64 = 3; + const SECOND_STARKNET_BLOCK_HASH: u64 = 0x3; + + const THIRD_ETHEREUM_BLOCK: u64 = 26; + const THIRD_STARKNET_BLOCK_NUMBER: u64 = 4; + const THIRD_STARKNET_BLOCK_HASH: u64 = 0x4; + + const FINAL_ETHEREUM_BLOCK: u64 = 31; + const INITIAL_STARKNET_BLOCK_NUMBER: u64 = 1; + + let initial_state = BlockHashAndNumber { + number: BlockNumber(INITIAL_STARKNET_BLOCK_NUMBER), + hash: BlockHash(StarkHash::from_hex_unchecked("0x0")), + }; + let first_sn_state_update = BlockHashAndNumber { + number: BlockNumber(FIRST_STARKNET_BLOCK_NUMBER), + hash: BlockHash(StarkHash::from_hex_unchecked(&format!( + "{:#x}", + FIRST_STARKNET_BLOCK_HASH + ))), + }; + let second_sn_state_update = BlockHashAndNumber { + number: BlockNumber(SECOND_STARKNET_BLOCK_NUMBER), + hash: BlockHash(StarkHash::from_hex_unchecked(&format!( + "{:#x}", + SECOND_STARKNET_BLOCK_HASH + ))), + }; + let third_sn_state_update = BlockHashAndNumber { + number: BlockNumber(THIRD_STARKNET_BLOCK_NUMBER), + hash: BlockHash(StarkHash::from_hex_unchecked(&format!( + "{:#x}", + THIRD_STARKNET_BLOCK_HASH + ))), + }; + + let base_layer = AnvilBaseLayer::new(None).await; + let provider = &base_layer.anvil_provider; + + let mut current_block = provider.get_block_number().await.expect("Failed to get block number"); + let mut prev_starknet_block_hash = 0u64; + + // Apply state updates. + if current_block < FIRST_ETHEREUM_BLOCK - 1 { + let blocks_to_mine = FIRST_ETHEREUM_BLOCK - 1 - current_block; + let _result: Option = provider + .raw_request("anvil_mine".into(), [blocks_to_mine]) + .await + .expect("Failed to mine blocks on Anvil"); + } + base_layer + .update_mocked_starknet_contract_state(MockedStateUpdate { + new_block_number: FIRST_STARKNET_BLOCK_NUMBER, + new_block_hash: FIRST_STARKNET_BLOCK_HASH, + prev_block_hash: prev_starknet_block_hash, + }) + .await + .expect("Failed to update state"); + current_block = FIRST_ETHEREUM_BLOCK; + prev_starknet_block_hash = FIRST_STARKNET_BLOCK_HASH; + + if current_block < SECOND_ETHEREUM_BLOCK - 1 { + let blocks_to_mine = SECOND_ETHEREUM_BLOCK - 1 - current_block; + let _result: Option = provider + .raw_request("anvil_mine".into(), [blocks_to_mine]) + .await + .expect("Failed to mine blocks on Anvil"); + } + base_layer + .update_mocked_starknet_contract_state(MockedStateUpdate { + new_block_number: SECOND_STARKNET_BLOCK_NUMBER, + new_block_hash: SECOND_STARKNET_BLOCK_HASH, + prev_block_hash: prev_starknet_block_hash, + }) + .await + .expect("Failed to update state"); + current_block = SECOND_ETHEREUM_BLOCK; + prev_starknet_block_hash = SECOND_STARKNET_BLOCK_HASH; + + if current_block < THIRD_ETHEREUM_BLOCK - 1 { + let blocks_to_mine = THIRD_ETHEREUM_BLOCK - 1 - current_block; + let _result: Option = provider + .raw_request("anvil_mine".into(), [blocks_to_mine]) + .await + .expect("Failed to mine blocks on Anvil"); + } + base_layer + .update_mocked_starknet_contract_state(MockedStateUpdate { + new_block_number: THIRD_STARKNET_BLOCK_NUMBER, + new_block_hash: THIRD_STARKNET_BLOCK_HASH, + prev_block_hash: prev_starknet_block_hash, + }) + .await + .expect("Failed to update state"); + current_block = THIRD_ETHEREUM_BLOCK; + + // Mine to the final Ethereum block. + if current_block < FINAL_ETHEREUM_BLOCK { + let blocks_to_mine = FINAL_ETHEREUM_BLOCK - current_block; + let _result: Option = provider + .raw_request("anvil_mine".into(), [blocks_to_mine]) + .await + .expect("Failed to mine blocks on Anvil"); + } + + // Finality constants: finality = FINAL_ETHEREUM_BLOCK - target_block + const FINALITY_LATEST_ETHEREUM_BLOCK: u64 = 0; + const FINALITY_AT_THIRD_UPDATE: u64 = FINAL_ETHEREUM_BLOCK - THIRD_ETHEREUM_BLOCK; + const FINALITY_AFTER_SECOND_BEFORE_THIRD: u64 = FINAL_ETHEREUM_BLOCK - THIRD_ETHEREUM_BLOCK + 1; + const FINALITY_AT_SECOND_UPDATE: u64 = FINAL_ETHEREUM_BLOCK - SECOND_ETHEREUM_BLOCK; + const FINALITY_AFTER_FIRST_BEFORE_SECOND: u64 = + FINAL_ETHEREUM_BLOCK - SECOND_ETHEREUM_BLOCK + 1; + const FINALITY_AT_FIRST_UPDATE: u64 = FINAL_ETHEREUM_BLOCK - FIRST_ETHEREUM_BLOCK; + const FINALITY_BEFORE_FIRST_UPDATE: u64 = FINAL_ETHEREUM_BLOCK - FIRST_ETHEREUM_BLOCK + 1; + const FINALITY_ERROR_CASE: u64 = FINAL_ETHEREUM_BLOCK + 10; // Error: finality too high + + type Scenario = (u64, Option); + let scenarios: Vec = vec![ + (FINALITY_LATEST_ETHEREUM_BLOCK, Some(third_sn_state_update)), + (FINALITY_AT_THIRD_UPDATE, Some(third_sn_state_update)), + (FINALITY_AFTER_SECOND_BEFORE_THIRD, Some(second_sn_state_update)), + (FINALITY_AT_SECOND_UPDATE, Some(second_sn_state_update)), + (FINALITY_AFTER_FIRST_BEFORE_SECOND, Some(first_sn_state_update)), + (FINALITY_AT_FIRST_UPDATE, Some(first_sn_state_update)), + (FINALITY_BEFORE_FIRST_UPDATE, Some(initial_state)), + (FINALITY_ERROR_CASE, None), + ]; + + for (finality, expected) in scenarios { + let latest_block = base_layer.latest_proved_block(finality).await; + match latest_block { + Ok(latest_block) => { + assert_eq!(latest_block, expected, "Failed at finality {}", finality) + } + Err(e) => { + assert_matches!( + e, + EthereumBaseLayerError::LatestBlockNumberReturnedTooLow(_, _), + "Expected error at finality {} but got: {:?}", + finality, + e + ); + } + } + } +} diff --git a/crates/apollo_integration_tests/tests/mocked_starknet_state_update_test.rs b/crates/apollo_integration_tests/tests/mocked_starknet_state_update_test.rs new file mode 100644 index 00000000000..bedec803935 --- /dev/null +++ b/crates/apollo_integration_tests/tests/mocked_starknet_state_update_test.rs @@ -0,0 +1,90 @@ +use alloy::primitives::{I256, U256}; +use alloy::providers::Provider; +use alloy::rpc::types::eth::Filter as EthEventFilter; +use alloy::sol_types::SolEventInterface; +use apollo_base_layer_tests::anvil_base_layer::{AnvilBaseLayer, MockedStateUpdate}; +use papyrus_base_layer::ethereum_base_layer_contract::Starknet; +use papyrus_base_layer::BaseLayerContract; +use pretty_assertions::assert_eq; +use starknet_api::block::{BlockHash, BlockHashAndNumber, BlockNumber}; + +#[tokio::test] +async fn test_mocked_starknet_state_update() { + let base_layer = AnvilBaseLayer::new(None).await; + + // Check that the contract was initialized (during the construction above). + let no_finality = 0; + let genesis_block_number = 1; + let genesis_block_hash = 0; + let initial_state = base_layer.latest_proved_block(no_finality).await.unwrap().unwrap(); + assert_eq!( + initial_state.number, + BlockNumber(genesis_block_number), + "Starknet contract was not initiailized." + ); + assert_eq!( + initial_state.hash, + BlockHash(genesis_block_hash.into()), + "Starknet contract was not initiailized." + ); + + // Negative flow: update state should always have sequential block numbers. + let wrong_next_block_number = genesis_block_number + 2; + let incorrect_new_block_number_result = base_layer + .update_mocked_starknet_contract_state(MockedStateUpdate { + new_block_number: wrong_next_block_number, + new_block_hash: 2, + prev_block_hash: genesis_block_hash, + }) + .await; + assert!( + incorrect_new_block_number_result + .unwrap_err() + .to_string() + .contains("INVALID_PREV_BLOCK_NUMBER") + ); + + // Happy flow. + let next_block_number = genesis_block_number + 1; + let new_block_hash = 2; + base_layer + .update_mocked_starknet_contract_state(MockedStateUpdate { + new_block_number: next_block_number, + new_block_hash, + prev_block_hash: genesis_block_hash, + }) + .await + .unwrap(); + + let updated_block_number_and_hash = + base_layer.latest_proved_block(no_finality).await.unwrap().unwrap(); + assert_eq!( + updated_block_number_and_hash, + BlockHashAndNumber { + number: BlockNumber(next_block_number), + hash: BlockHash(new_block_hash.into()) + } + ); + + // Check that LogStateUpdate event was emitted (we don't use this event in the sequencer at the + // time this was written). + let event = base_layer + .ethereum_base_layer + .contract + .provider() + .get_logs(&EthEventFilter::new().from_block(1)) + .await + .unwrap(); + let event = event.first().unwrap(); + + match Starknet::StarknetEvents::decode_log(&event.inner).unwrap().data { + Starknet::StarknetEvents::LogStateUpdate(state_update) => { + assert_eq!( + state_update.blockNumber, + I256::from_dec_str(&next_block_number.to_string()).unwrap(), + ); + assert_eq!(state_update.blockHash, U256::from(new_block_hash)); + } + _ => panic!("Expected LogStateUpdate event"), + } +} diff --git a/crates/apollo_integration_tests/tests/test_custom_cairo0_txs.rs b/crates/apollo_integration_tests/tests/test_custom_cairo0_txs.rs index 2dbed33fe96..08afd2a3144 100644 --- a/crates/apollo_integration_tests/tests/test_custom_cairo0_txs.rs +++ b/crates/apollo_integration_tests/tests/test_custom_cairo0_txs.rs @@ -14,21 +14,20 @@ use starknet_api::test_utils::invoke::rpc_invoke_tx; use starknet_api::transaction::fields::{ContractAddressSalt, TransactionSignature}; use starknet_api::{calldata, felt}; -use crate::common::{end_to_end_flow, validate_tx_count, TestScenario}; +use crate::common::{end_to_end_flow, validate_tx_count, EndToEndFlowArgs, TestScenario}; mod common; const CUSTOM_CAIRO_0_INVOKE_TX_COUNT: usize = 9; -#[tokio::test] +/// The test uses 3 threads: 1 for the test's main thread and 2 for the sequencers. +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn custom_cairo0_txs() { - end_to_end_flow( + end_to_end_flow(EndToEndFlowArgs::new( TestIdentifier::EndToEndFlowTestCustomCairo0Txs, create_custom_cairo0_txs_scenario(), GasAmount(110000000), - false, - false, - ) + )) .await } diff --git a/crates/apollo_integration_tests/tests/test_custom_cairo1_txs.rs b/crates/apollo_integration_tests/tests/test_custom_cairo1_txs.rs index c3a70126c36..b83a21fb9bd 100644 --- a/crates/apollo_integration_tests/tests/test_custom_cairo1_txs.rs +++ b/crates/apollo_integration_tests/tests/test_custom_cairo1_txs.rs @@ -19,22 +19,21 @@ use starknet_api::transaction::TransactionVersion; use starknet_api::{calldata, felt}; use starknet_types_core::felt::Felt; -use crate::common::{end_to_end_flow, validate_tx_count, TestScenario}; +use crate::common::{end_to_end_flow, validate_tx_count, EndToEndFlowArgs, TestScenario}; mod common; const CUSTOM_INVOKE_TX_COUNT: usize = 16; /// Test a wide range of different kinds of invoke transactions. -#[tokio::test] +/// Number of threads is 3 = Num of sequencer + 1 for the test thread. +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn custom_cairo1_txs() { - end_to_end_flow( + end_to_end_flow(EndToEndFlowArgs::new( TestIdentifier::EndToEndFlowTestCustomSyscallInvokeTxs, create_custom_cairo1_txs_scenario(), BouncerWeights::default().proving_gas, - false, - false, - ) + )) .await } diff --git a/crates/apollo_integration_tests/tests/test_many.rs b/crates/apollo_integration_tests/tests/test_many.rs index ca8b98e4a60..989288d8344 100644 --- a/crates/apollo_integration_tests/tests/test_many.rs +++ b/crates/apollo_integration_tests/tests/test_many.rs @@ -5,19 +5,21 @@ use starknet_api::execution_resources::GasAmount; use starknet_api::rpc_transaction::RpcTransaction; use starknet_api::transaction::TransactionHash; -use crate::common::{end_to_end_flow, TestScenario}; +use crate::common::{end_to_end_flow, EndToEndFlowArgs, TestScenario}; mod common; /// This test checks that at least one block is full. -#[tokio::test] +/// The test uses 3 threads: 1 for the test's main thread and 2 for the sequencers. +#[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn many_txs_fill_at_least_one_block() { end_to_end_flow( - TestIdentifier::EndToEndFlowTestManyTxs, - create_many_txs_scenario(), - GasAmount(40000000), - true, - false, + EndToEndFlowArgs::new( + TestIdentifier::EndToEndFlowTestManyTxs, + create_many_txs_scenario(), + GasAmount(40000000), + ) + .expecting_full_blocks(), ) .await } diff --git a/crates/apollo_l1_endpoint_monitor/src/monitor.rs b/crates/apollo_l1_endpoint_monitor/src/monitor.rs index 7d52586e26f..78e75192a84 100644 --- a/crates/apollo_l1_endpoint_monitor/src/monitor.rs +++ b/crates/apollo_l1_endpoint_monitor/src/monitor.rs @@ -15,6 +15,9 @@ pub mod l1_endpoint_monitor_tests; // a bug in infura where the connectivity was fine, but get_block_number() failed. pub const HEALTH_CHECK_RPC_METHOD: &str = "eth_blockNumber"; +/// The minimum expected L1 block number for a valid endpoint response. +pub const MIN_EXPECTED_BLOCK_NUMBER: u64 = 1000; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct L1EndpointMonitor { pub current_l1_endpoint_index: usize, @@ -41,10 +44,11 @@ impl L1EndpointMonitor { for offset in 1..n_urls { let idx = (current_l1_endpoint_index + offset) % n_urls; if self.is_operational(idx).await { + // TODO(guyn): print the end point without the API key (use to_safe_string) warn!( "L1 endpoint {} down; switched to {}", - self.get_node_url(current_l1_endpoint_index), - self.get_node_url(idx) + to_safe_string(self.get_node_url(current_l1_endpoint_index)), + to_safe_string(self.get_node_url(idx)) ); self.current_l1_endpoint_index = idx; @@ -69,7 +73,7 @@ impl L1EndpointMonitor { // high-level readability (through a dedicated const) and to improve testability. async fn is_operational(&self, l1_endpoint_index: usize) -> bool { let l1_endpoint_url = self.get_node_url(l1_endpoint_index); - let l1_client = ProviderBuilder::new().on_http(l1_endpoint_url.clone()); + let l1_client = ProviderBuilder::new().connect_http(l1_endpoint_url.clone()); let l1_endpoint_url = to_safe_string(l1_endpoint_url); // Note: response type annotation is coupled with the rpc method used. @@ -88,7 +92,16 @@ impl L1EndpointMonitor { error!("L1 endpoint {l1_endpoint_url} is not operational: {e}"); false } - Ok(Ok(_)) => { + Ok(Ok(block_number)) => { + // TODO(guyn): remove this once we understand where these low numbers are coming + // from. + if block_number < U64::from(MIN_EXPECTED_BLOCK_NUMBER) { + warn!( + "L1 endpoint {l1_endpoint_url} is operational, but block number is too \ + low: {block_number}" + ); + } + info_every_n!(1000, "L1 endpoint {l1_endpoint_url} is operational"); true } diff --git a/crates/apollo_l1_endpoint_monitor/tests/happy_flow.rs b/crates/apollo_l1_endpoint_monitor/tests/happy_flow.rs index 36ec56893cb..58a226d6978 100644 --- a/crates/apollo_l1_endpoint_monitor/tests/happy_flow.rs +++ b/crates/apollo_l1_endpoint_monitor/tests/happy_flow.rs @@ -1,7 +1,7 @@ +use alloy::node_bindings::{Anvil, AnvilInstance}; use apollo_l1_endpoint_monitor::monitor::L1EndpointMonitor; use apollo_l1_endpoint_monitor_config::config::L1EndpointMonitorConfig; use apollo_l1_endpoint_monitor_types::L1EndpointMonitorError; -use papyrus_base_layer::test_utils::anvil; use url::Url; /// Integration test: two Anvil nodes plus a bogus endpoint to exercise cycling and failure. @@ -11,9 +11,10 @@ async fn end_to_end_cycle_and_recovery() { // IMPORTANT: This is one of the only cases where two anvil nodes are needed simultaneously, // since we are flow testing two separate L1 nodes. Other tests should never use more than one // at a time! - let good_node_1 = anvil(None); + let good_node_1 = anvil(); let good_url_1 = good_node_1.endpoint_url(); - let good_node_2 = anvil(None); + let good_node_2 = anvil(); + let good_url_2 = good_node_2.endpoint_url(); // Bogus endpoint on port 1 that is likely to be unbound, see the unit tests for more details. @@ -56,7 +57,7 @@ async fn end_to_end_cycle_and_recovery() { } // ANVIL node 1 has risen! - let good_node_1 = anvil(None); + let good_node_1 = anvil(); // Anvil is configured to use an ephemeral port, so this new node will be bound to a fresh port. // We cannot reuse the previous URL since the old port may no longer be available. let good_url_1 = good_node_1.endpoint_url(); @@ -66,3 +67,11 @@ async fn end_to_end_cycle_and_recovery() { assert_eq!(active3, good_url_1); assert_eq!(monitor.current_l1_endpoint_index, 1); } + +// IMPORTANT: this util is needed since this specific test must run two L1 instances by definition, +// for all other integration tests that need anvil use "anvil base layer". +fn anvil() -> AnvilInstance { + Anvil::new() + .try_spawn() + .expect("Anvil not installed, see anvil base layer for installation instructions.") +} diff --git a/crates/apollo_l1_gas_price/src/l1_gas_price_scraper.rs b/crates/apollo_l1_gas_price/src/l1_gas_price_scraper.rs index a17f63bb202..14b125317db 100644 --- a/crates/apollo_l1_gas_price/src/l1_gas_price_scraper.rs +++ b/crates/apollo_l1_gas_price/src/l1_gas_price_scraper.rs @@ -89,10 +89,8 @@ impl L1GasPriceScraper { &mut self, block_number: &mut L1BlockNumber, ) -> L1GasPriceScraperResult<(), B> { - let Some(last_block_number) = self.latest_l1_block_number().await? else { - // Not enough blocks under current finality. Try again later. - return Ok(()); - }; + let last_block_number = self.latest_l1_block_number().await?; + trace!("Scraping gas prices starting from block {} to {last_block_number}.", *block_number,); info_every_n_sec!( 1, @@ -147,7 +145,7 @@ impl L1GasPriceScraper { Ok(()) } - async fn latest_l1_block_number(&self) -> L1GasPriceScraperResult, B> { + async fn latest_l1_block_number(&self) -> L1GasPriceScraperResult { self.base_layer .latest_l1_block_number(self.config.finality) .await @@ -169,7 +167,6 @@ where let latest = self .latest_l1_block_number() .await - .expect("Failed to get the latest L1 block number at startup") .expect("Failed to get the latest L1 block number at startup"); // If no starting block is provided, the default is to start from diff --git a/crates/apollo_l1_gas_price/src/l1_gas_price_scraper_test.rs b/crates/apollo_l1_gas_price/src/l1_gas_price_scraper_test.rs index c0c756890bf..21bf8ddb32c 100644 --- a/crates/apollo_l1_gas_price/src/l1_gas_price_scraper_test.rs +++ b/crates/apollo_l1_gas_price/src/l1_gas_price_scraper_test.rs @@ -47,7 +47,7 @@ fn setup_scraper( expected_number_of_blocks: usize, ) -> L1GasPriceScraper { let mut mock_contract = MockBaseLayerContract::new(); - mock_contract.expect_latest_l1_block_number().returning(move |_| Ok(Some(end_block))); + mock_contract.expect_latest_l1_block_number().returning(move |_| Ok(end_block)); mock_contract.expect_get_block_header().returning(move |block_number| { if block_number >= end_block { Ok(None) @@ -89,7 +89,7 @@ async fn run_l1_gas_price_scraper_two_blocks() { // Explicitly making the mocks here, so we can customize them for the test. let mut mock_contract = MockBaseLayerContract::new(); // Note the order of the expectation is important! Can only scrape the first blocks first. - mock_contract.expect_latest_l1_block_number().returning(move |_| Ok(Some(END_BLOCK2))); + mock_contract.expect_latest_l1_block_number().returning(move |_| Ok(END_BLOCK2)); mock_contract .expect_get_block_header() .times(usize::try_from(END_BLOCK1 - START_BLOCK + 1).unwrap()) @@ -154,7 +154,7 @@ async fn l1_reorg_gas_price_scraper_error() { // Explicitly making the mocks here, so we can customize them for the test. let mut mock_contract = MockBaseLayerContract::new(); // Note the order of the expectation is important! Can only scrape the first blocks first. - mock_contract.expect_latest_l1_block_number().returning(move |_| Ok(Some(END_BLOCK2))); + mock_contract.expect_latest_l1_block_number().returning(move |_| Ok(END_BLOCK2)); mock_contract .expect_get_block_header() .times(usize::try_from(END_BLOCK1 - START_BLOCK + 1).unwrap()) @@ -234,7 +234,7 @@ async fn l1_short_reorg_gas_price_scraper_is_fine(#[case] finality: u64) { // This expectation just returns the last block number we want (which is end_of_chain-finality). mock_contract .expect_latest_l1_block_number() - .returning(move |finality| Ok(Some(end_of_chain_clone.load(Ordering::SeqCst) - finality))); + .returning(move |finality| Ok(end_of_chain_clone.load(Ordering::SeqCst) - finality)); // This expectation will return the regular chain, or the chain with the reorg (depending on // has_reorg_happened). mock_contract.expect_get_block_header().returning(move |block_number| { diff --git a/crates/apollo_l1_provider/Cargo.toml b/crates/apollo_l1_provider/Cargo.toml index 3ba6b6c1e8b..48d24b63343 100644 --- a/crates/apollo_l1_provider/Cargo.toml +++ b/crates/apollo_l1_provider/Cargo.toml @@ -33,10 +33,10 @@ starknet_api.workspace = true thiserror.workspace = true tokio.workspace = true tracing.workspace = true -url.workspace = true [dev-dependencies] alloy.workspace = true +apollo_base_layer_tests.workspace = true apollo_batcher_types = { workspace = true, features = ["testing"] } apollo_l1_endpoint_monitor_types = { workspace = true, features = ["testing"] } apollo_l1_provider_types = { workspace = true, features = ["testing"] } diff --git a/crates/apollo_l1_provider/src/bootstrapper.rs b/crates/apollo_l1_provider/src/bootstrapper.rs index bf1757a43c0..0649c29f82c 100644 --- a/crates/apollo_l1_provider/src/bootstrapper.rs +++ b/crates/apollo_l1_provider/src/bootstrapper.rs @@ -14,7 +14,7 @@ use tracing::{debug, error, info}; pub type LazyCatchUpHeight = Arc>; -/// Cache's commits to be applied later. This flow is only relevant while the node is starting up. +/// Caches commits to be applied later. This flow is only relevant while the node is starting up. #[derive(Clone)] pub struct Bootstrapper { /// The catch-up height for the bootstrapper is the batcher height (unless overridden @@ -153,8 +153,6 @@ impl std::fmt::Debug for Bootstrapper { } } -// TODO(noamsp): fix catch up height to use batcher height and not the latest block number in -// storage. async fn l2_sync_task( l1_provider_client: SharedL1ProviderClient, batcher_client: SharedBatcherClient, diff --git a/crates/apollo_l1_provider/src/l1_provider.rs b/crates/apollo_l1_provider/src/l1_provider.rs index 2b61e80bcd7..64c0a38906c 100644 --- a/crates/apollo_l1_provider/src/l1_provider.rs +++ b/crates/apollo_l1_provider/src/l1_provider.rs @@ -45,6 +45,102 @@ pub struct L1Provider { } impl L1Provider { + // Functions Called by the scraper. + + // Start the provider, get first-scrape events, start L2 sync. + pub async fn initialize(&mut self, events: Vec) -> L1ProviderResult<()> { + info!("Initializing l1 provider"); + let Some(bootstrapper) = self.state.get_bootstrapper() else { + // FIXME: This should be return FatalError or similar, which should trigger a planned + // restart from the infra, since this CAN happen if the scraper recovered from a crash. + // Right now this is effectively a KILL message when called in steady state. + panic!("Called initialize while not in bootstrap state. Restart service."); + }; + bootstrapper.start_l2_sync(self.current_height).await; + self.add_events(events)?; + + Ok(()) + } + + /// Accept new events from the scraper. + #[instrument(skip_all, err)] + pub fn add_events(&mut self, events: Vec) -> L1ProviderResult<()> { + if self.state.uninitialized() { + return Err(L1ProviderError::Uninitialized); + } + + // TODO(guyn): can we remove this "every sec" since the polling interval is rather long? + info_every_n_sec!(1, "Adding {} l1 events", events.len()); + trace!("Adding events: {events:?}"); + + for event in events { + match event { + Event::L1HandlerTransaction { + l1_handler_tx, + block_timestamp, + scrape_timestamp, + } => { + let tx_hash = l1_handler_tx.tx_hash; + let successfully_inserted = + self.tx_manager.add_tx(l1_handler_tx, block_timestamp, scrape_timestamp); + if !successfully_inserted { + debug!( + "Unexpected L1 Handler transaction with hash: {tx_hash}, already \ + known or committed." + ); + } + } + Event::TransactionCancellationStarted { + tx_hash, + cancellation_request_timestamp, + } => { + if !self.tx_manager.exists(tx_hash) { + warn!( + "Dropping cancellation request for old L1 handler transaction \ + {tx_hash}: not in the provider and will never be scraped at this \ + point." + ); + continue; + } + + self.tx_manager + .request_cancellation(tx_hash, cancellation_request_timestamp) + .inspect(|previous_request_timestamp| { + // Re-requesting a cancellation is meaningful for the L1 timelock, but + // for the l2 timelock we only consider the first cancellation + // relevant. + info!( + "Dropping duplicated cancellation request for {tx_hash} at \ + {cancellation_request_timestamp}, previous request block \ + timestamp still stands: {previous_request_timestamp}" + ); + }); + } + Event::TransactionCanceled(event_data) => { + // TODO(guyn): delete the transaction from the provider. + info!( + "Cancellation finalized with data: {event_data:?}. THIS DOES NOT DELETE \ + THE TRANSACTION FROM THE PROVIDER YET." + ); + } + Event::TransactionConsumed { tx_hash, timestamp: consumed_at } => { + if let Err(previously_consumed_at) = + self.tx_manager.consume_tx(tx_hash, consumed_at, self.clock.unix_now()) + { + panic!( + "Double consumption of {tx_hash} at {consumed_at}, previously \ + consumed at {previously_consumed_at}." + ); + } + } + } + } + Ok(()) + } + + // Functions Called by the batcher. + + /// Start a new block as either proposer or validator. #[instrument(skip(self), err)] pub fn start_block( &mut self, @@ -55,28 +151,15 @@ impl L1Provider { return Err(L1ProviderError::Uninitialized); } - self.validate_height(height)?; + self.check_height_with_error(height)?; info!("Starting block at height: {height}"); self.state = state.into(); self.tx_manager.start_block(); Ok(()) } - pub async fn initialize(&mut self, events: Vec) -> L1ProviderResult<()> { - info!("Initializing l1 provider"); - let Some(bootstrapper) = self.state.get_bootstrapper() else { - // FIXME: This should be return FatalError or similar, which should trigger a planned - // restart from the infra, since this CAN happen if the scraper recovered from a crash. - // Right now this is effectively a KILL message when called in steady state. - panic!("Called initialize while not in bootstrap state. Restart service."); - }; - bootstrapper.start_l2_sync(self.current_height).await; - self.add_events(events)?; - - Ok(()) - } - /// Retrieves up to `n_txs` transactions that have yet to be proposed or accepted on L2. + /// Used to make new proposals. Must be in Propose state. #[instrument(skip(self), err)] pub fn get_txs( &mut self, @@ -87,7 +170,7 @@ impl L1Provider { return Err(L1ProviderError::Uninitialized); } - self.validate_height(height)?; + self.check_height_with_error(height)?; match self.state { ProviderState::Propose => { @@ -103,15 +186,20 @@ impl L1Provider { ); Ok(txs) } - ProviderState::Pending | ProviderState::Bootstrap(_) => { - Err(L1ProviderError::OutOfSessionGetTransactions) + ProviderState::Pending => { + panic!( + "get_txs called while in pending state. Panicking in order to restart the \ + provider and bootstrap again." + ); } + ProviderState::Bootstrap(_) => Err(L1ProviderError::OutOfSessionGetTransactions), ProviderState::Validate => Err(L1ProviderError::GetTransactionConsensusBug), } } /// Returns true if and only if the given transaction is both not included in an L2 block, and - /// unconsumed on L1. + /// unconsumed on L1. Validator should call validate on each tx during validation. + /// Must be in Validate state. #[instrument(skip(self), err)] pub fn validate( &mut self, @@ -122,20 +210,26 @@ impl L1Provider { return Err(L1ProviderError::Uninitialized); } - self.validate_height(height)?; + self.check_height_with_error(height)?; match self.state { ProviderState::Validate => { Ok(self.tx_manager.validate_tx(tx_hash, self.clock.unix_now())) } ProviderState::Propose => Err(L1ProviderError::ValidateTransactionConsensusBug), - ProviderState::Pending | ProviderState::Bootstrap(_) => { - Err(L1ProviderError::OutOfSessionValidate) + ProviderState::Pending => { + panic!( + "validate called while in pending state. Panicking in order to restart the \ + provider and bootstrap again." + ); } + ProviderState::Bootstrap(_) => Err(L1ProviderError::OutOfSessionValidate), } } // TODO(Gilad): when deciding on consensus, if possible, have commit_block also tell the node if // it's about to [optimistically-]propose or validate the next block. + /// Upon successfully committing a block, commit all committed/rejected transactions, unstage + /// any remaining transactions, and put provider back in Pending state. #[instrument(skip(self), err)] pub fn commit_block( &mut self, @@ -148,6 +242,9 @@ impl L1Provider { return Err(L1ProviderError::Uninitialized); } + // TODO(guyn): this message is misleading, it checks start_height, not current_height. + // TODO(guyn): maybe we should indeed ignore all blocks below current_height? + // See other todo in bootstrap(). if self.is_historical_height(height) { debug!( "Skipping commit block for historical height: {}, current height is higher: {}", @@ -156,109 +253,26 @@ impl L1Provider { return Ok(()); } + // Reroute this block to bootstrapper, either adding it to the backlog, or applying it and + // ending the bootstrap. if self.state.is_bootstrapping() { // Once bootstrap completes it will transition to Pending state by itself. return self.bootstrap(committed_txs, height); } - self.validate_height(height)?; + // If not historical height and not bootstrapping, must go into bootstrap state upon getting + // wrong height. + // TODO(guyn): for now, we go into bootstrap using panic. We should improve this. + self.check_height_with_panic(height); self.apply_commit_block(committed_txs, rejected_txs); self.state = self.state.transition_to_pending(); Ok(()) } - #[instrument(skip_all, err)] - pub fn add_events(&mut self, events: Vec) -> L1ProviderResult<()> { - if self.state.uninitialized() { - return Err(L1ProviderError::Uninitialized); - } - - info_every_n_sec!(1, "Adding {} l1 events", events.len()); - trace!("Adding events: {events:?}"); - - for event in events { - match event { - Event::L1HandlerTransaction { - l1_handler_tx, - block_timestamp, - scrape_timestamp, - } => { - let tx_hash = l1_handler_tx.tx_hash; - let successfully_inserted = - self.tx_manager.add_tx(l1_handler_tx, block_timestamp, scrape_timestamp); - if !successfully_inserted { - debug!( - "Unexpected L1 Handler transaction with hash: {tx_hash}, already \ - known or committed." - ); - } - } - Event::TransactionCancellationStarted { - tx_hash, - cancellation_request_timestamp, - } => { - if !self.tx_manager.exists(tx_hash) { - warn!( - "Dropping cancellation request for old L1 handler transaction \ - {tx_hash}: not in the provider and will never be scraped at this \ - point." - ); - continue; - } - - self.tx_manager - .request_cancellation(tx_hash, cancellation_request_timestamp) - .inspect(|previous_request_timestamp| { - // Re-requesting a cancellation is meaningful for the L1 timelock, but - // for the l2 timelock we only consider the first cancellation - // relevant. - info!( - "Dropping duplicated cancellation request for {tx_hash} at \ - {cancellation_request_timestamp}, previous request block \ - timestamp still stands: {previous_request_timestamp}" - ); - }); - } - Event::TransactionConsumed { tx_hash, timestamp: consumed_at } => { - if let Err(previously_consumed_at) = - self.tx_manager.consume_tx(tx_hash, consumed_at, self.clock.unix_now()) - { - panic!( - "Double consumption of {tx_hash} at {consumed_at}, previously \ - consumed at {previously_consumed_at}." - ); - } - } - _ => return Err(L1ProviderError::unsupported_l1_event(event)), - } - } - Ok(()) - } - - pub fn get_l1_provider_snapshot(&self) -> L1ProviderResult { - let txs_snapshot = self.tx_manager.snapshot(); - Ok(L1ProviderSnapshot { - uncommitted_transactions: txs_snapshot.uncommitted, - uncommitted_staged_transactions: txs_snapshot.uncommitted_staged, - rejected_transactions: txs_snapshot.rejected, - rejected_staged_transactions: txs_snapshot.rejected_staged, - committed_transactions: txs_snapshot.committed, - l1_provider_state: self.state.as_str().to_string(), - current_height: self.current_height, - }) - } - - fn validate_height(&mut self, height: BlockNumber) -> L1ProviderResult<()> { - if height != self.current_height { - return Err(L1ProviderError::UnexpectedHeight { - expected_height: self.current_height, - got: height, - }); - } - Ok(()) - } + // Functions called internally. + /// Commit the given transactions, and increment the current height. fn apply_commit_block( &mut self, consumed_txs: IndexSet, @@ -272,7 +286,11 @@ impl L1Provider { self.current_height = self.current_height.unchecked_next(); } - /// Try to apply commit_block backlog, and if all caught up, drop bootstrapping state. + /// Any commit_block call gets rerouted to this function when in bootstrap state. + /// - If block number is higher than current height, block is backlogged. + /// - If provider gets a block consistent with current_height, apply it and then the rest of the + /// backlog, then transition to Pending state. + /// - Blocks lower than current height are checked for consistency with existing transactions. fn bootstrap( &mut self, committed_txs: IndexSet, @@ -287,6 +305,9 @@ impl L1Provider { match new_height.cmp(¤t_height) { // This is likely a bug in the batcher/sync, it should never be _behind_ the provider. Less => { + // TODO(guyn): check if this is reliable: old blocks can have txs that were + // committed then consumed and deleted. We should probably decide to always log and + // ignore old blocks or always return an error. let diff_from_already_committed: Vec<_> = committed_txs .iter() .copied() @@ -314,6 +335,7 @@ impl L1Provider { })? } } + // TODO(guyn): check what about rejected txs here and in the backlog? Equal => self.apply_commit_block(committed_txs, Default::default()), // We're still syncing, backlog it, it'll get applied later. Greater => { @@ -356,8 +378,8 @@ impl L1Provider { backlog.iter().map(|commit_block| commit_block.height).collect::>() ); - for commit_block in backlog { - self.apply_commit_block(commit_block.committed_txs, Default::default()); + for committed_block in backlog { + self.apply_commit_block(committed_block.committed_txs, Default::default()); } info!( @@ -373,10 +395,50 @@ impl L1Provider { Ok(()) } + fn check_height_with_panic(&mut self, height: BlockNumber) { + if height > self.current_height { + // TODO(shahak): Add a way to move to bootstrap mode from any point and move to + // bootstrap here instead of panicking. + panic!( + "Batcher surpassed l1 provider. Panicking in order to restart the provider and \ + bootstrap again. l1 provider height: {}, batcher height: {}", + self.current_height, height + ); + } + if height < self.current_height { + panic!("Unexpected height: expected >= {}, got {}", self.current_height, height); + } + } + + fn check_height_with_error(&mut self, height: BlockNumber) -> L1ProviderResult<()> { + if height != self.current_height { + return Err(L1ProviderError::UnexpectedHeight { + expected_height: self.current_height, + got: height, + }); + } + Ok(()) + } + /// Checks if the given height appears before the timeline of which the provider is aware of. fn is_historical_height(&self, height: BlockNumber) -> bool { height < self.start_height } + + // Functions used for debugging or testing. + + pub fn get_l1_provider_snapshot(&self) -> L1ProviderResult { + let txs_snapshot = self.tx_manager.snapshot(); + Ok(L1ProviderSnapshot { + uncommitted_transactions: txs_snapshot.uncommitted, + uncommitted_staged_transactions: txs_snapshot.uncommitted_staged, + rejected_transactions: txs_snapshot.rejected, + rejected_staged_transactions: txs_snapshot.rejected_staged, + committed_transactions: txs_snapshot.committed, + l1_provider_state: self.state.as_str().to_string(), + current_height: self.current_height, + }) + } } impl PartialEq for L1Provider { diff --git a/crates/apollo_l1_provider/src/l1_provider_tests.rs b/crates/apollo_l1_provider/src/l1_provider_tests.rs index 66e7db4231d..6d837e36308 100644 --- a/crates/apollo_l1_provider/src/l1_provider_tests.rs +++ b/crates/apollo_l1_provider/src/l1_provider_tests.rs @@ -1,3 +1,4 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::Arc; use std::time::Duration; @@ -210,7 +211,7 @@ fn process_events_committed_txs() { } #[test] -fn pending_state_errors() { +fn pending_state_panics() { // Setup. let mut l1_provider = L1ProviderContentBuilder::new() .with_state(ProviderState::Pending) @@ -218,14 +219,11 @@ fn pending_state_errors() { .build_into_l1_provider(); // Test. - assert_matches!( - l1_provider.get_txs(1, BlockNumber(0)).unwrap_err(), - L1ProviderError::OutOfSessionGetTransactions - ); + assert!(catch_unwind(AssertUnwindSafe(|| { l1_provider.get_txs(1, BlockNumber(0)) })).is_err()); - assert_matches!( - l1_provider.validate(tx_hash!(1), BlockNumber(0)).unwrap_err(), - L1ProviderError::OutOfSessionValidate + assert!( + catch_unwind(AssertUnwindSafe(|| { l1_provider.validate(tx_hash!(1), BlockNumber(0)) })) + .is_err() ); } diff --git a/crates/apollo_l1_provider/src/l1_scraper.rs b/crates/apollo_l1_provider/src/l1_scraper.rs index 9a8a4c8c57d..045e53bcc12 100644 --- a/crates/apollo_l1_provider/src/l1_scraper.rs +++ b/crates/apollo_l1_provider/src/l1_scraper.rs @@ -21,7 +21,6 @@ use crate::metrics::{ register_scraper_metrics, L1_MESSAGE_SCRAPER_BASELAYER_ERROR_COUNT, L1_MESSAGE_SCRAPER_REORG_DETECTED, - L1_MESSAGE_SCRAPER_SECONDS_SINCE_LAST_SUCCESSFUL_SCRAPE, L1_MESSAGE_SCRAPER_SUCCESS_COUNT, }; @@ -31,6 +30,7 @@ pub mod l1_scraper_tests; type L1ScraperResult = Result>; +// TODO(guyn): make this a config parameter // Sensible lower bound. const L1_BLOCK_TIME: u64 = 10; @@ -41,7 +41,6 @@ pub struct L1Scraper { pub l1_provider_client: SharedL1ProviderClient, tracked_event_identifiers: Vec, pub clock: Arc, - last_successful_scrape_timestamp: Option, } impl L1Scraper { @@ -59,14 +58,19 @@ impl L1Scraper { config, tracked_event_identifiers: events_identifiers_to_track.to_vec(), clock: Arc::new(DefaultClock), - last_successful_scrape_timestamp: None, }) } + /// Send an initialize message to the L1 provider, including events scraped on the first + /// iteration. The provider will return an error if it was already initialized (e.g., if the + /// scraper was restarted). #[instrument(skip(self), err)] async fn initialize(&mut self) -> L1ScraperResult<(), B> { let (latest_l1_block, events) = self.fetch_events().await?; + debug!("Latest L1 block for initialize: {latest_l1_block:?}"); + debug!("All events scraped during initialize: {events:?}"); + // If this gets too high, send in batches. let initialize_result = self.l1_provider_client.initialize(events).await; handle_client_error(initialize_result)?; @@ -76,16 +80,18 @@ impl L1Scraper { Ok(()) } + /// Scrape recent events and send them to the L1 provider. pub async fn send_events_to_l1_provider(&mut self) -> L1ScraperResult<(), B> { self.assert_no_l1_reorgs().await?; let (latest_l1_block, events) = self.fetch_events().await?; + // TODO(guyn): remove these _every_n_sec because the polling interval is longer. trace!("scraped up to {latest_l1_block:?}"); info_every_n_sec!(1, "scraped up to {latest_l1_block:?}"); // Sending even if there are no events, to keep the flow as simple/debuggable as possible. - // Perf hit is minimal, since the scraper is on the same machine as the provider (no net). - // If this gets spammy, short-circuit on events.empty(). + // Perf hit is minimal, since the scraper is on the same machine as the provider (no + // network). If this gets spammy, short-circuit on events.empty(). let add_events_result = self.l1_provider_client.add_events(events).await; handle_client_error(add_events_result)?; @@ -94,6 +100,7 @@ impl L1Scraper { Ok(()) } + /// Query the L1 base layer for all events since last_l1_block_processed. async fn fetch_events(&self) -> L1ScraperResult<(L1BlockReference, Vec), B> { let scrape_timestamp = self.clock.unix_now(); @@ -104,10 +111,20 @@ impl L1Scraper { .map_err(L1ScraperError::BaseLayerError)?; let Some(latest_l1_block) = latest_l1_block else { + // TODO(guyn): get rid of finality_too_high, use a better error. return Err( L1ScraperError::finality_too_high(self.config.finality, &self.base_layer).await ); }; + // This can happen if, e.g., changing base layers. Should ignore and try scraping again. + if latest_l1_block.number <= self.last_l1_block_processed.number { + warn!( + "Latest L1 block number {} is not greater than the last processed L1 block number \ + {}. Ignoring, will try again on the next interval.", + latest_l1_block.number, self.last_l1_block_processed.number + ); + return Ok((self.last_l1_block_processed, vec![])); + } let scraping_start_number = self.last_l1_block_processed.number + 1; let scraping_result = self @@ -116,7 +133,7 @@ impl L1Scraper { .await; let l1_events = scraping_result.map_err(L1ScraperError::BaseLayerError)?; - // Used for debug. + // Used for debug. Collect the L1 tx hashes and L1 block timestamps. let l1_messages_info = l1_events .iter() .filter_map(|event| match event { @@ -127,6 +144,7 @@ impl L1Scraper { }) .collect::>(); + // Convert L1 events into Starknet provider events. Includes calculating the L2 tx hash. let events = l1_events .into_iter() .map(|event| { @@ -135,7 +153,7 @@ impl L1Scraper { }) .collect::, _>>()?; - // Used for debug. + // Used for debug. Collect the L2 hashes for events that are L1 handler transactions. let l2_hashes = events.iter().filter_map(|event| match event { Event::L1HandlerTransaction { l1_handler_tx, .. } => Some(l1_handler_tx.tx_hash), _ => None, @@ -143,7 +161,7 @@ impl L1Scraper { let formatted_pairs = zip_eq(l1_messages_info, l2_hashes) .map(|((l1_hash, timestamp), l2_hash)| { - format!("L1 hash: {l1_hash:?}, L1 timestamp: {timestamp}, L2 hash: {l2_hash}") + format!("L1 tx hash: {l1_hash:?}, L1 timestamp: {timestamp}, L2 tx hash: {l2_hash}") }) .collect::>(); if formatted_pairs.is_empty() { @@ -151,6 +169,23 @@ impl L1Scraper { } else { debug!("Got Messages to L2: {formatted_pairs:?}"); } + + // Debug: log cancellation started events. + let cancellation_started_events = events + .iter() + .filter_map(|event| match event { + Event::TransactionCancellationStarted { tx_hash, .. } => Some(*tx_hash), + _ => None, + }) + .collect::>(); + let formatted_cancellation_started_events = cancellation_started_events + .iter() + .map(|tx_hash| format!("Cancel tx with L2 hash: {tx_hash}")); + if cancellation_started_events.is_empty() { + debug_every_n!(100, "Got Cancellation Started Events: []"); + } else { + debug!("Got Cancellation Started Events: {formatted_cancellation_started_events:?}"); + } Ok((latest_l1_block, events)) } @@ -171,6 +206,7 @@ impl L1Scraper { } loop { + // TODO(guyn): move sleep to the end of the loop. sleep(self.config.polling_interval_seconds).await; match self.send_events_to_l1_provider().await { @@ -179,23 +215,19 @@ impl L1Scraper { warn!("BaseLayerError during scraping: {e:?}"); } Ok(_) => { - self.last_successful_scrape_timestamp = Some(self.clock.unix_now()); L1_MESSAGE_SCRAPER_SUCCESS_COUNT.increment(1); } Err(e) => return Err(e), } - - // Can reach this if Ok or if baselayer error. Other cases would return Err, crashing - // the node. - if let Some(last_successful_scrape_timestamp) = self.last_successful_scrape_timestamp { - L1_MESSAGE_SCRAPER_SECONDS_SINCE_LAST_SUCCESSFUL_SCRAPE - .set_lossy(self.clock.unix_now() - last_successful_scrape_timestamp); - } } } + /// Fetch last_l1_block_processed again, check it still exists and that its hash is the same. + /// If a reorg occurred up to this block, return an error (existing data in the provider is + /// stale). async fn assert_no_l1_reorgs(&self) -> L1ScraperResult<(), B> { let last_processed_l1_block_number = self.last_l1_block_processed.number; + let last_processed_l1_block_hash = self.last_l1_block_processed.hash; let last_block_processed_fresh = self .base_layer .l1_block_at(last_processed_l1_block_number) @@ -206,13 +238,13 @@ impl L1Scraper { L1_MESSAGE_SCRAPER_REORG_DETECTED.increment(1); return Err(L1ScraperError::L1ReorgDetected { reason: format!( - "Last processed L1 block with number {last_processed_l1_block_number} no \ - longer exists." + "Last processed L1 block with number {last_processed_l1_block_number} and \ + hash {last_processed_l1_block_hash} no longer exists." ), }); }; - if last_block_processed_fresh.hash != self.last_l1_block_processed.hash { + if last_block_processed_fresh.hash != last_processed_l1_block_hash { L1_MESSAGE_SCRAPER_REORG_DETECTED.increment(1); return Err(L1ScraperError::L1ReorgDetected { reason: format!( @@ -220,7 +252,7 @@ impl L1Scraper { hash stored, {}", last_block_processed_fresh.hash, last_processed_l1_block_number, - self.last_l1_block_processed.hash, + last_processed_l1_block_hash ), }); } @@ -229,6 +261,8 @@ impl L1Scraper { } } +/// Use config.startup_rewind_time_seconds to estimate an L1 block number +/// that is far enough back to start scraping from. pub async fn fetch_start_block( base_layer: &B, config: &L1ScraperConfig, @@ -238,27 +272,31 @@ pub async fn fetch_start_block( .latest_l1_block_number(finality) .await .map_err(L1ScraperError::BaseLayerError)?; - - let latest_l1_block = match latest_l1_block_number { - Some(latest_l1_block_number) => Ok(latest_l1_block_number), - None => Err(L1ScraperError::finality_too_high(finality, base_layer).await), - }?; + debug!("Latest L1 block number: {latest_l1_block_number:?}"); // Estimate the number of blocks in the interval, to rewind from the latest block. let blocks_in_interval = config.startup_rewind_time_seconds.as_secs() / L1_BLOCK_TIME; + debug!("Blocks in interval: {blocks_in_interval}"); + // Add 50% safety margin. let safe_blocks_in_interval = blocks_in_interval + blocks_in_interval / 2; + debug!("Safe blocks in interval: {safe_blocks_in_interval}"); - let l1_block_number_rewind = latest_l1_block.saturating_sub(safe_blocks_in_interval); + let l1_block_number_rewind = latest_l1_block_number.saturating_sub(safe_blocks_in_interval); + debug!("L1 block number rewind: {l1_block_number_rewind}"); let block_reference_rewind = base_layer .l1_block_at(l1_block_number_rewind) .await .map_err(L1ScraperError::BaseLayerError)? - .expect( - "Rewound L1 block number is between 0 and the verified latest L1 block, so should \ - exist", - ); + .unwrap_or_else(|| { + panic!( + "Rewound L1 block number is between 0 and the verified latest L1 block \ + {latest_l1_block_number}, so should exist", + ) + }); + debug!("Block reference rewind: {block_reference_rewind:?}"); + Ok(block_reference_rewind) } @@ -275,7 +313,10 @@ impl ComponentStarter for L1Scraper { pub enum L1ScraperError { #[error("Base layer error: {0}")] BaseLayerError(T::Error), - #[error("Finality too high: {finality:?} > {latest_l1_block_no_finality:?}")] + #[error( + "Could not find block number. Finality {finality:?}, latest block: \ + {latest_l1_block_no_finality:?}" + )] FinalityTooHigh { finality: u64, latest_l1_block_no_finality: L1BlockNumber }, #[error("Failed to calculate hash: {0}")] HashCalculationError(StarknetApiError), @@ -308,13 +349,15 @@ impl PartialEq for L1ScraperError { } } +// TODO(guyn): get rid of finality_too_high, use a better error. impl L1ScraperError { + /// Pass any base layer errors. In the rare case that the finality is bigger than the latest L1 + /// block number, return FinalityTooHigh. pub async fn finality_too_high(finality: u64, base_layer: &B) -> L1ScraperError { let latest_l1_block_number_no_finality = base_layer.latest_l1_block_number(0).await; let latest_l1_block_no_finality = match latest_l1_block_number_no_finality { - Ok(block_number) => block_number - .expect("Latest *L1* block without finality is assumed to always exist."), + Ok(block_number) => block_number, Err(error) => return Self::BaseLayerError(error), }; @@ -335,13 +378,6 @@ fn handle_client_error( L1ProviderClientError::L1ProviderError(L1ProviderError::Uninitialized) => { Err(L1ScraperError::NeedsRestart) } - L1ProviderClientError::L1ProviderError(L1ProviderError::UnsupportedL1Event(event)) => { - panic!( - "Scraper-->Provider consistency error: the event {event} is not supported by the \ - provider, but has been scraped and sent to it nonetheless. Check the list of \ - tracked events in the scraper and compare to the provider's." - ) - } error => panic!("Unexpected error: {error}"), } } diff --git a/crates/apollo_l1_provider/src/l1_scraper_tests.rs b/crates/apollo_l1_provider/src/l1_scraper_tests.rs index 0e0d3d9ce22..22daed8af22 100644 --- a/crates/apollo_l1_provider/src/l1_scraper_tests.rs +++ b/crates/apollo_l1_provider/src/l1_scraper_tests.rs @@ -2,12 +2,11 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::Duration; -use alloy::primitives::U256; use apollo_batcher_types::batcher_types::GetHeightResponse; use apollo_batcher_types::communication::MockBatcherClient; use apollo_infra::trace_util::configure_tracing; use apollo_l1_provider_types::errors::L1ProviderError; -use apollo_l1_provider_types::{Event, L1ProviderClient, MockL1ProviderClient}; +use apollo_l1_provider_types::{L1ProviderClient, MockL1ProviderClient}; use apollo_l1_scraper_config::config::L1ScraperConfig; use apollo_state_sync_types::communication::MockStateSyncClient; use apollo_state_sync_types::errors::StateSyncError; @@ -15,34 +14,14 @@ use apollo_state_sync_types::state_sync_types::SyncBlock; use assert_matches::assert_matches; use indexmap::IndexSet; use itertools::Itertools; -use papyrus_base_layer::ethereum_base_layer_contract::{ - EthereumBaseLayerConfig, - EthereumBaseLayerContract, - Starknet, -}; -use papyrus_base_layer::test_utils::{ - anvil_instance_from_url, - ethereum_base_layer_config_for_anvil, -}; -use papyrus_base_layer::{BaseLayerContract, L1BlockHash, L1BlockReference, MockBaseLayerContract}; +use papyrus_base_layer::{L1BlockHash, L1BlockReference, MockBaseLayerContract}; use rstest::{fixture, rstest}; -use starknet_api::block::{BlockNumber, BlockTimestamp}; -use starknet_api::contract_address; -use starknet_api::core::{EntryPointSelector, Nonce}; -use starknet_api::executable_transaction::L1HandlerTransaction as ExecutableL1HandlerTransaction; -use starknet_api::hash::StarkHash; -use starknet_api::transaction::fields::{Calldata, Fee}; -use starknet_api::transaction::{ - L1HandlerTransaction, - TransactionHash, - TransactionHasher, - TransactionVersion, -}; -use url::Url; +use starknet_api::block::BlockNumber; +use starknet_api::transaction::TransactionHash; use crate::bootstrapper::Bootstrapper; use crate::l1_provider::{L1Provider, L1ProviderBuilder}; -use crate::l1_scraper::{fetch_start_block, L1Scraper, L1ScraperError}; +use crate::l1_scraper::{L1Scraper, L1ScraperError}; use crate::test_utils::FakeL1ProviderClient; use crate::{event_identifiers_to_track, L1ProviderConfig}; @@ -75,158 +54,6 @@ fn receive_commit_block( l1_provider.commit_block(committed.iter().copied().collect(), [].into(), height).unwrap(); } -// TODO(Gilad): Replace EthereumBaseLayerContract with a mock that has a provider initialized with -// `with_recommended_fillers`, in order to be able to create txs from non-default users. -async fn scraper( - base_layer_config: EthereumBaseLayerConfig, - base_layer_url: Url, -) -> (L1Scraper, Arc) { - let fake_client = Arc::new(FakeL1ProviderClient::default()); - let base_layer = EthereumBaseLayerContract::new(base_layer_config, base_layer_url); - let l1_scraper_config = L1ScraperConfig::default(); - - let l1_start_block = fetch_start_block(&base_layer, &l1_scraper_config).await.unwrap(); - // Deploy a fresh Starknet contract on Anvil from the bytecode in the JSON file. - Starknet::deploy(base_layer.contract.provider().clone()).await.unwrap(); - - let scraper = L1Scraper::new( - l1_scraper_config, - fake_client.clone(), - base_layer, - event_identifiers_to_track(), - l1_start_block, - ) - .await - .unwrap(); - - (scraper, fake_client) -} - -#[tokio::test] -// TODO(Gilad): extract setup stuff into test helpers once more tests are added and patterns emerge. -async fn txs_happy_flow() { - if !in_ci() { - return; - } - - let (base_layer_config, base_layer_url) = ethereum_base_layer_config_for_anvil(None); - let anvil = anvil_instance_from_url(&base_layer_url); - // Setup. - let (mut scraper, fake_client) = scraper(base_layer_config, base_layer_url).await; - - // Test. - // Scrape multiple events. - let l2_contract_address = "0x12"; - let l2_entry_point = "0x34"; - - let message_to_l2_0 = scraper.base_layer.contract.sendMessageToL2( - l2_contract_address.parse().unwrap(), - l2_entry_point.parse().unwrap(), - vec![U256::from(1_u8), U256::from(2_u8)], - ); - let message_to_l2_1 = scraper.base_layer.contract.sendMessageToL2( - l2_contract_address.parse().unwrap(), - l2_entry_point.parse().unwrap(), - vec![U256::from(3_u8), U256::from(4_u8)], - ); - let nonce_of_message_to_l2_0 = U256::from(0_u8); - let request_cancel_message_0 = scraper.base_layer.contract.startL1ToL2MessageCancellation( - l2_contract_address.parse().unwrap(), - l2_entry_point.parse().unwrap(), - vec![U256::from(1_u8), U256::from(2_u8)], - nonce_of_message_to_l2_0, - ); - - // Send the transactions. - let mut block_timestamps: Vec = Vec::with_capacity(2); - for msg in &[message_to_l2_0, message_to_l2_1] { - let receipt = msg.send().await.unwrap().get_receipt().await.unwrap(); - block_timestamps.push( - scraper - .base_layer - .get_block_header(receipt.block_number.unwrap()) - .await - .unwrap() - .unwrap() - .timestamp, - ); - } - - let cancel_receipt = - request_cancel_message_0.send().await.unwrap().get_receipt().await.unwrap(); - let cancel_timestamp = scraper - .base_layer - .get_block_header(cancel_receipt.block_number.unwrap()) - .await - .unwrap() - .unwrap() - .timestamp; - - const EXPECTED_VERSION: TransactionVersion = TransactionVersion(StarkHash::ZERO); - let default_anvil_l1_account_address: StarkHash = - StarkHash::from_bytes_be_slice(anvil.addresses()[0].as_slice()); - let expected_internal_l1_tx = L1HandlerTransaction { - version: EXPECTED_VERSION, - nonce: Nonce(StarkHash::ZERO), - contract_address: contract_address!(l2_contract_address), - entry_point_selector: EntryPointSelector(StarkHash::from_hex_unchecked(l2_entry_point)), - calldata: Calldata( - vec![default_anvil_l1_account_address, StarkHash::ONE, StarkHash::from(2)].into(), - ), - }; - let tx_hash_first_tx = expected_internal_l1_tx - .calculate_transaction_hash(&scraper.config.chain_id, &EXPECTED_VERSION) - .unwrap(); - let tx = ExecutableL1HandlerTransaction { - tx_hash: expected_internal_l1_tx - .calculate_transaction_hash(&scraper.config.chain_id, &EXPECTED_VERSION) - .unwrap(), - tx: expected_internal_l1_tx, - paid_fee_on_l1: Fee(0), - }; - let first_expected_log = Event::L1HandlerTransaction { - l1_handler_tx: tx.clone(), - block_timestamp: block_timestamps[0], - scrape_timestamp: block_timestamps[0].0, - }; - - let expected_internal_l1_tx_2 = L1HandlerTransaction { - nonce: Nonce(StarkHash::ONE), - calldata: Calldata( - vec![default_anvil_l1_account_address, StarkHash::from(3), StarkHash::from(4)].into(), - ), - ..tx.tx - }; - let second_expected_log = Event::L1HandlerTransaction { - l1_handler_tx: ExecutableL1HandlerTransaction { - tx_hash: expected_internal_l1_tx_2 - .calculate_transaction_hash(&scraper.config.chain_id, &EXPECTED_VERSION) - .unwrap(), - tx: expected_internal_l1_tx_2, - ..tx - }, - block_timestamp: block_timestamps[1], - scrape_timestamp: block_timestamps[1].0, - }; - - let expected_cancel_message = Event::TransactionCancellationStarted { - tx_hash: tx_hash_first_tx, - cancellation_request_timestamp: cancel_timestamp, - }; - - // Assert. - scraper.send_events_to_l1_provider().await.unwrap(); - fake_client.assert_add_events_received_with(&[ - first_expected_log, - second_expected_log, - expected_cancel_message, - ]); - - // Previous events had been scraped, should no longer appear. - scraper.send_events_to_l1_provider().await.unwrap(); - fake_client.assert_add_events_received_with(&[]); -} - // TODO(Gilad): figure out how To setup anvil on a specific L1 block (through genesis.json?) and // with a specified L2 block logged to L1 (hopefully without having to use real backup). /// This test simulates a bootstrapping flow, in which 3 blocks are synced from L2, during which two @@ -584,7 +411,7 @@ async fn provider_crash_should_crash_scraper(mut dummy_base_layer: MockBaseLayer #[fixture] fn dummy_base_layer() -> MockBaseLayerContract { let mut base_layer = MockBaseLayerContract::new(); - base_layer.expect_latest_l1_block_number().return_once(|_| Ok(Some(Default::default()))); + base_layer.expect_latest_l1_block_number().return_once(|_| Ok(Default::default())); base_layer.expect_latest_l1_block().return_once(|_| Ok(Some(Default::default()))); base_layer.expect_events().return_once(|_, _| Ok(Default::default())); base_layer @@ -664,6 +491,60 @@ async fn l1_reorg_block_number(mut dummy_base_layer: MockBaseLayerContract) { ); } +#[tokio::test] +async fn latest_block_number_goes_down() { + // Setup. + const L1_LATEST_BLOCK_NUMBER: u64 = 10; + const L1_BLOCK_HASH: L1BlockHash = L1BlockHash([123; 32]); + const L1_BAD_LATEST_NUMBER: u64 = 5; + + let mut l1_provider_client = MockL1ProviderClient::default(); + l1_provider_client.expect_add_events().returning(|_| Ok(())); + + let mut dummy_base_layer: MockBaseLayerContract = MockBaseLayerContract::new(); + + // This should always be returned, even if we set the "response" to a lower block number. + let expected_block_reference = + L1BlockReference { number: L1_LATEST_BLOCK_NUMBER, hash: L1_BLOCK_HASH }; + + let latest_l1_block_response = Arc::new(Mutex::new(expected_block_reference)); + let latest_l1_block_response_clone = latest_l1_block_response.clone(); + dummy_base_layer + .expect_latest_l1_block() + .times(2) + .returning(move |_| Ok(Some(*latest_l1_block_response_clone.lock().unwrap()))); + + dummy_base_layer.expect_events().times(1).returning(|_, _| Ok(vec![])); + + dummy_base_layer + .expect_l1_block_at() + .returning(move |number| Ok(Some(L1BlockReference { number, hash: L1_BLOCK_HASH }))); + + let mut scraper = L1Scraper::new( + L1ScraperConfig::default(), + Arc::new(l1_provider_client), + dummy_base_layer, + event_identifiers_to_track(), + L1BlockReference { number: 0, hash: L1_BLOCK_HASH }, + ) + .await + .unwrap(); + + // Test. + // can send messages to the provider. + // This should also set the scraper's last_l1_block_processed to block number 10. + assert_eq!(scraper.send_events_to_l1_provider().await, Ok(())); + + // Simulate a base layer returning a lower block number. + latest_l1_block_response.lock().unwrap().number = L1_BAD_LATEST_NUMBER; + + // Make sure we don't hit the reorg error in this scenario. + scraper.assert_no_l1_reorgs().await.unwrap(); + + // Should ignore and try again on the next interval, returning the same block reference. + assert_eq!(scraper.fetch_events().await, Ok((expected_block_reference, vec![]))); +} + #[test] #[ignore = "similar to backlog_happy_flow, only shorter, and sprinkle some start_block/get_txs \ attempts while its bootstrapping (and assert failure on height), then assert that they \ diff --git a/crates/apollo_l1_provider/src/lib.rs b/crates/apollo_l1_provider/src/lib.rs index 9a08e6d7403..03142f5c107 100644 --- a/crates/apollo_l1_provider/src/lib.rs +++ b/crates/apollo_l1_provider/src/lib.rs @@ -15,8 +15,9 @@ pub use apollo_l1_provider_config::config::L1ProviderConfig; use apollo_l1_provider_types::SessionState; use papyrus_base_layer::constants::{ EventIdentifier, - CONSUMED_MESSAGE_TO_L1_EVENT_IDENTIFIER, + CONSUMED_MESSAGE_TO_L2_EVENT_IDENTIFIER, LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER, + MESSAGE_TO_L2_CANCELED_EVENT_IDENTIFIER, MESSAGE_TO_L2_CANCELLATION_STARTED_EVENT_IDENTIFIER, }; @@ -26,9 +27,14 @@ use crate::transaction_manager::TransactionManagerConfig; /// Current state of the provider, where pending means: idle, between proposal/validation cycles. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ProviderState { + /// Provider is not read for proposing or validating. Use start_block to transition to Propose + /// or Validate. Pending, + /// Provider is ready for proposing. Use commit_block to finish and return to Pending. Propose, + /// Provider is catching up using sync. Only happens on startup. Bootstrap(Bootstrapper), + /// Provider is ready for validating. Use validate to validate a transaction. Validate, } @@ -105,6 +111,7 @@ pub const fn event_identifiers_to_track() -> &'static [EventIdentifier] { &[ LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER, MESSAGE_TO_L2_CANCELLATION_STARTED_EVENT_IDENTIFIER, - CONSUMED_MESSAGE_TO_L1_EVENT_IDENTIFIER, + MESSAGE_TO_L2_CANCELED_EVENT_IDENTIFIER, + CONSUMED_MESSAGE_TO_L2_EVENT_IDENTIFIER, ] } diff --git a/crates/apollo_l1_provider/src/metrics.rs b/crates/apollo_l1_provider/src/metrics.rs index e95d0e9b5e0..28cea92e910 100644 --- a/crates/apollo_l1_provider/src/metrics.rs +++ b/crates/apollo_l1_provider/src/metrics.rs @@ -15,7 +15,6 @@ define_metrics!( MetricCounter { L1_MESSAGE_SCRAPER_SUCCESS_COUNT, "l1_message_scraper_success_count", "Number of times the L1 message scraper successfully scraped messages and updated the provider", init=0 }, MetricCounter { L1_MESSAGE_SCRAPER_BASELAYER_ERROR_COUNT, "l1_message_scraper_baselayer_error_count", "Number of times the L1 message scraper encountered an error while scraping the base layer", init=0}, MetricCounter { L1_MESSAGE_SCRAPER_REORG_DETECTED, "l1_message_scraper_reorg_detected", "Number of times the L1 message scraper detected a reorganization in the base layer", init=0}, - MetricGauge { L1_MESSAGE_SCRAPER_SECONDS_SINCE_LAST_SUCCESSFUL_SCRAPE, "l1_message_scraper_seconds_since_last_successful_scrape", "Number of seconds since the last successful scrape of the L1 message scraper"}, }, ); @@ -23,5 +22,4 @@ pub(crate) fn register_scraper_metrics() { L1_MESSAGE_SCRAPER_SUCCESS_COUNT.register(); L1_MESSAGE_SCRAPER_BASELAYER_ERROR_COUNT.register(); L1_MESSAGE_SCRAPER_REORG_DETECTED.register(); - L1_MESSAGE_SCRAPER_SECONDS_SINCE_LAST_SUCCESSFUL_SCRAPE.register(); } diff --git a/crates/apollo_l1_provider/tests/flow_tests.rs b/crates/apollo_l1_provider/tests/flow_tests.rs new file mode 100644 index 00000000000..e3c5e70edb9 --- /dev/null +++ b/crates/apollo_l1_provider/tests/flow_tests.rs @@ -0,0 +1,205 @@ +use std::sync::Arc; +use std::time::Duration; + +use alloy::primitives::U256; +use apollo_base_layer_tests::anvil_base_layer::AnvilBaseLayer; +use apollo_batcher_types::communication::MockBatcherClient; +use apollo_infra::component_client::LocalComponentClient; +use apollo_infra::component_definitions::{ComponentStarter, RequestWrapper}; +use apollo_infra::component_server::{ + ComponentServerStarter, + LocalComponentServer, + LocalServerConfig, +}; +use apollo_l1_provider::l1_provider::L1ProviderBuilder; +use apollo_l1_provider::l1_scraper::L1Scraper; +use apollo_l1_provider::metrics::L1_PROVIDER_INFRA_METRICS; +use apollo_l1_provider::{event_identifiers_to_track, L1ProviderConfig}; +use apollo_l1_provider_types::{ + Event, + L1ProviderClient, + L1ProviderRequest, + L1ProviderResponse, + SessionState, + ValidationStatus, +}; +use apollo_l1_scraper_config::config::L1ScraperConfig; +use apollo_state_sync_types::communication::MockStateSyncClient; +use apollo_state_sync_types::state_sync_types::SyncBlock; +use papyrus_base_layer::test_utils::anvil_mine_blocks; +use papyrus_base_layer::{BaseLayerContract, L1BlockHash, L1BlockNumber, L1BlockReference}; +use starknet_api::block::{BlockNumber, BlockTimestamp}; +use starknet_api::core::ChainId; +use tokio::sync::mpsc::channel; + +// Must wait at least 1 second, as timestamps are integer seconds. +const COOLDOWN_DURATION: Duration = Duration::from_millis(1000); +const WAIT_FOR_L1_DURATION: Duration = Duration::from_millis(10); +const NUMBER_OF_BLOCKS_TO_MINE: u64 = 100; +const CHAIN_ID: ChainId = ChainId::Mainnet; + +const START_L1_BLOCK: L1BlockReference = L1BlockReference { number: 0, hash: L1BlockHash([0; 32]) }; +const START_L1_BLOCK_NUMBER: L1BlockNumber = START_L1_BLOCK.number; +const START_L2_HEIGHT: BlockNumber = BlockNumber(0); +const TARGET_L2_HEIGHT: BlockNumber = BlockNumber(1); + +#[tokio::test] +async fn flow_tests() { + // Setup. + // Setup the state sync client. + let mut state_sync_client = MockStateSyncClient::default(); + state_sync_client.expect_get_block().returning(move |_| Ok(SyncBlock::default())); + + // Setup the base layer. + let base_layer = AnvilBaseLayer::new(None).await; + let contract = &base_layer.ethereum_base_layer.contract; + anvil_mine_blocks( + base_layer.ethereum_base_layer.config.clone(), + NUMBER_OF_BLOCKS_TO_MINE, + &base_layer.ethereum_base_layer.get_url().await.expect("Failed to get anvil url."), + ) + .await; + + let finality = 0; + let last_l1_block_number = + base_layer.ethereum_base_layer.latest_l1_block_number(finality).await.unwrap(); + assert!(last_l1_block_number > START_L1_BLOCK_NUMBER + NUMBER_OF_BLOCKS_TO_MINE); + + // Send message from L1 to L2. + let l2_contract_address = "0x12"; + let l2_entry_point = "0x34"; + let call_data = vec![U256::from(1_u8), U256::from(2_u8)]; + let fee = 1_u8; + let message_to_l2 = contract + .sendMessageToL2( + l2_contract_address.parse().unwrap(), + l2_entry_point.parse().unwrap(), + call_data, + ) + .value(U256::from(fee)); + message_to_l2.call().await.unwrap(); // Query for errors. + let receipt = message_to_l2.send().await.unwrap().get_receipt().await.unwrap(); + let message_timestamp = base_layer + .get_block_header(receipt.block_number.unwrap()) + .await + .unwrap() + .unwrap() + .timestamp; + assert!(message_timestamp > BlockTimestamp(0)); + + // Make sure the L1 event was posted to Anvil + let finality = 0; + let last_l1_block_number = + base_layer.ethereum_base_layer.latest_l1_block_number(finality).await.unwrap(); + assert!(last_l1_block_number > START_L1_BLOCK_NUMBER + NUMBER_OF_BLOCKS_TO_MINE); + let event_filter = event_identifiers_to_track(); + let mut events = base_layer + .ethereum_base_layer + .events( + // Include last block with message. + START_L1_BLOCK_NUMBER..=last_l1_block_number + 1, + event_filter, + ) + .await + .unwrap(); + assert!(events.len() == 1); + let l1_event = events.pop().unwrap(); + + // Convert the L1 event to an Apollo event, so we can get the L2 hash. + let l1_event_converted = + apollo_l1_provider_types::Event::from_l1_event(&CHAIN_ID, l1_event, message_timestamp.0) + .unwrap(); + let Event::L1HandlerTransaction { l1_handler_tx, .. } = l1_event_converted else { + panic!("L1 event converted is not a L1 handler transaction"); + }; + let l2_hash = l1_handler_tx.tx_hash; + + // Set up the L1 provider client and server. + // This channel connects the L1Provider client to the server. + let (tx, rx) = channel::>(32); + + // Create the provider client. + let l1_provider_client = + LocalComponentClient::new(tx, L1_PROVIDER_INFRA_METRICS.get_local_client_metrics()); + + // L1 provider setup. + let l1_provider_config = L1ProviderConfig { + new_l1_handler_cooldown_seconds: COOLDOWN_DURATION, + ..Default::default() + }; + let l1_provider = L1ProviderBuilder::new( + l1_provider_config, + Arc::new(l1_provider_client.clone()), + Arc::new(MockBatcherClient::default()), // Consider saving a copy of this to interact + Arc::new(state_sync_client), + ) + .startup_height(START_L2_HEIGHT) + .catchup_height(TARGET_L2_HEIGHT) + .build(); + + // Create the server. + let mut l1_provider_server = LocalComponentServer::new( + l1_provider, + &LocalServerConfig::default(), + rx, + L1_PROVIDER_INFRA_METRICS.get_local_server_metrics(), + ); + // Start the server: + tokio::spawn(async move { + l1_provider_server.start().await; + }); + + // Set up the L1 scraper and run it as a server. + let l1_scraper_config = L1ScraperConfig { + polling_interval_seconds: COOLDOWN_DURATION, + chain_id: CHAIN_ID, + ..Default::default() + }; + let mut scraper = L1Scraper::new( + l1_scraper_config, + Arc::new(l1_provider_client.clone()), + base_layer, + &[], + START_L1_BLOCK, + ) + .await + .expect("Should be able to create the scraper"); + + // Run the scraper in a separate task. + tokio::spawn(async move { + scraper.start().await; + }); + tokio::time::sleep(WAIT_FOR_L1_DURATION).await; + + // Test. + let next_block_height = BlockNumber(TARGET_L2_HEIGHT.0 + 1); + + // Check that we can validate this message even though no time has passed. + l1_provider_client.start_block(SessionState::Validate, next_block_height).await.unwrap(); + assert_eq!( + l1_provider_client.validate(l2_hash, next_block_height).await.unwrap(), + ValidationStatus::Validated + ); + + // Test that we do not propose anything before the cooldown is over. + l1_provider_client.start_block(SessionState::Propose, next_block_height).await.unwrap(); + let n_txs = 1; + let txs = l1_provider_client.get_txs(n_txs, next_block_height).await.unwrap(); + assert!(txs.is_empty()); + + // Sleep at least one second more than the cooldown to make sure we are not failing due to + // fractional seconds. + tokio::time::sleep(COOLDOWN_DURATION + Duration::from_secs(1)).await; + + // Test that we propose after the cooldown is over. + l1_provider_client.start_block(SessionState::Propose, next_block_height).await.unwrap(); + let txs = l1_provider_client.get_txs(n_txs, next_block_height).await.unwrap(); + assert!(!txs.is_empty()); + + // Check that we can validate this message after the cooldown, too. + l1_provider_client.start_block(SessionState::Validate, next_block_height).await.unwrap(); + assert_eq!( + l1_provider_client.validate(l2_hash, next_block_height).await.unwrap(), + ValidationStatus::Validated + ); +} diff --git a/crates/apollo_l1_provider_types/src/errors.rs b/crates/apollo_l1_provider_types/src/errors.rs index 2523e6d2308..9de7017d861 100644 --- a/crates/apollo_l1_provider_types/src/errors.rs +++ b/crates/apollo_l1_provider_types/src/errors.rs @@ -5,8 +5,6 @@ use serde::{Deserialize, Serialize}; use starknet_api::block::BlockNumber; use thiserror::Error; -use crate::Event; - #[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum L1ProviderError { #[error("`get_txs` while in `Validate` state")] @@ -26,8 +24,6 @@ pub enum L1ProviderError { UnexpectedHeight { expected_height: BlockNumber, got: BlockNumber }, #[error("Cannot transition from {from} to {to}")] UnexpectedProviderStateTransition { from: String, to: String }, - #[error("L1 event not supported: {0}")] - UnsupportedL1Event(String), #[error("`validate` called while in `Propose` state")] ValidateTransactionConsensusBug, } @@ -36,10 +32,6 @@ impl L1ProviderError { pub fn unexpected_transition(from: impl ToString, to: impl ToString) -> Self { Self::UnexpectedProviderStateTransition { from: from.to_string(), to: to.to_string() } } - - pub fn unsupported_l1_event(event: Event) -> Self { - Self::UnsupportedL1Event(event.to_string()) - } } #[derive(Clone, Debug, Error)] diff --git a/crates/apollo_l1_provider_types/src/lib.rs b/crates/apollo_l1_provider_types/src/lib.rs index 52482ff3131..1cef43e47be 100644 --- a/crates/apollo_l1_provider_types/src/lib.rs +++ b/crates/apollo_l1_provider_types/src/lib.rs @@ -54,6 +54,7 @@ pub enum InvalidValidationStatus { ConsumedOnL1, // This tx is either never been seen or was seen, consumed, and deleted. NotFound, + L1ProviderError, } #[derive(Serialize, Deserialize, Clone, AsRefStr, EnumDiscriminants)] diff --git a/crates/apollo_mempool/Cargo.toml b/crates/apollo_mempool/Cargo.toml index ec86e18bf83..d3386644788 100644 --- a/crates/apollo_mempool/Cargo.toml +++ b/crates/apollo_mempool/Cargo.toml @@ -12,6 +12,8 @@ testing = [ "apollo_network/testing", "apollo_network_types/testing", "apollo_time/testing", + "blockifier/testing", + "blockifier_test_utils", "starknet_api/testing", ] @@ -28,6 +30,7 @@ apollo_metrics.workspace = true apollo_network_types.workspace = true apollo_time = { workspace = true } async-trait.workspace = true +blockifier_test_utils = { workspace = true, optional = true } derive_more.workspace = true indexmap.workspace = true rand.workspace = true @@ -45,6 +48,9 @@ apollo_network_types = { workspace = true, features = ["testing"] } apollo_test_utils.workspace = true apollo_time = { workspace = true, features = ["testing"] } assert_matches.workspace = true +blockifier = { workspace = true, features = ["mocks", "testing"] } +blockifier_test_utils.workspace = true +criterion = { workspace = true, features = ["async_tokio"] } itertools.workspace = true mempool_test_utils.workspace = true metrics.workspace = true @@ -55,3 +61,9 @@ rstest.workspace = true starknet-types-core.workspace = true starknet_api = { workspace = true, features = ["testing"] } tokio.workspace = true + +[[bench]] +harness = false +name = "apollo_mempool" +path = "benches/main.rs" +required-features = ["testing"] diff --git a/crates/apollo_mempool/benches/main.rs b/crates/apollo_mempool/benches/main.rs new file mode 100644 index 00000000000..a78b40b5910 --- /dev/null +++ b/crates/apollo_mempool/benches/main.rs @@ -0,0 +1,44 @@ +//! Benchmark suite for the Apollo mempool crate. +//! +//! This module provides tools to measure the performance of the mempool service under various +//! transaction loads and configurations. +//! +//! The main benchmark, `invoke_benchmark`, evaluates how efficiently the mempool processes randomly +//! generated invoke transactions across different scenarios. +//! +//! To run the benchmarks, use: `cargo bench --bench apollo_mempool`. +/// import the Mempool test utilities. +mod utils; + +use apollo_mempool_config::config::MempoolConfig; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use utils::{BenchTestSetup, BenchTestSetupConfig}; + +fn run_invoke_benchmark(criterion: &mut Criterion, config: &BenchTestSetupConfig) { + let test_setup = BenchTestSetup::new(config); + let id_param = format!("{} txs with chunk size {}", config.n_txs, config.chunk_size); + criterion.bench_with_input( + BenchmarkId::new("invoke", id_param), + &test_setup, + |bencher, test_setup| { + bencher + .to_async(tokio::runtime::Runtime::new().unwrap()) + .iter(|| test_setup.mempool_add_get_txs()); + }, + ); +} + +fn invoke_benchmarks(criterion: &mut Criterion) { + let configs = [BenchTestSetupConfig { + n_txs: 10000, + chunk_size: 100, + mempool_config: MempoolConfig::default(), + }]; + + for config in configs.iter() { + run_invoke_benchmark(criterion, config); + } +} + +criterion_group!(benches, invoke_benchmarks); +criterion_main!(benches); diff --git a/crates/apollo_mempool/benches/utils.rs b/crates/apollo_mempool/benches/utils.rs new file mode 100644 index 00000000000..6c134d932fd --- /dev/null +++ b/crates/apollo_mempool/benches/utils.rs @@ -0,0 +1,319 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use apollo_config_manager_types::communication::MockConfigManagerClient; +use apollo_infra::component_server::{ComponentServerStarter, LocalServerConfig}; +use apollo_infra::metrics::{LocalClientMetrics, LocalServerMetrics}; +use apollo_mempool::communication::{create_mempool, LocalMempoolServer}; +use apollo_mempool_config::config::MempoolConfig; +use apollo_mempool_p2p_types::communication::{ + MempoolP2pPropagatorClient, + MempoolP2pPropagatorClientResult, +}; +use apollo_mempool_types::communication::{ + AddTransactionArgsWrapper, + LocalMempoolClient, + SharedMempoolClient, +}; +use apollo_mempool_types::mempool_types::{AccountState, AddTransactionArgs, CommitBlockArgs}; +use apollo_metrics::metrics::{LabeledMetricHistogram, MetricCounter, MetricGauge, MetricScope}; +use apollo_network_types::network_types::BroadcastedMessageMetadata; +use async_trait::async_trait; +use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1}; +use blockifier_test_utils::contracts::FeatureContract; +use indexmap::IndexSet; +use mempool_test_utils::starknet_api_test_utils::MultiAccountTransactionGenerator; +use starknet_api::core::{ContractAddress, Nonce}; +use starknet_api::rpc_transaction::{ + InternalRpcTransaction, + InternalRpcTransactionWithoutTxHash, + RpcTransaction, +}; +use starknet_api::{nonce, tx_hash}; +use tokio::sync::mpsc; + +/// Minimal overhead P2P propagator for benchmarking +/// All methods return Ok(()) immediately without any processing +pub struct BenchMempoolP2pPropagator; + +#[async_trait] +impl MempoolP2pPropagatorClient for BenchMempoolP2pPropagator { + async fn add_transaction( + &self, + _transaction: InternalRpcTransaction, + ) -> MempoolP2pPropagatorClientResult<()> { + Ok(()) + } + + async fn continue_propagation( + &self, + _propagation_metadata: BroadcastedMessageMetadata, + ) -> MempoolP2pPropagatorClientResult<()> { + Ok(()) + } + + async fn broadcast_queued_transactions(&self) -> MempoolP2pPropagatorClientResult<()> { + Ok(()) + } +} + +// Dummy metrics for benchmarking (we don't need real metrics collection) +const BENCH_MSGS_RECEIVED: MetricCounter = MetricCounter::new( + MetricScope::Infra, + "bench_msgs_received", + "Benchmark messages received", + 0, // initial_value +); + +const BENCH_MSGS_PROCESSED: MetricCounter = MetricCounter::new( + MetricScope::Infra, + "bench_msgs_processed", + "Benchmark messages processed", + 0, // initial_value +); + +const BENCH_HIGH_PRIORITY_QUEUE_DEPTH: MetricGauge = MetricGauge::new( + MetricScope::Infra, + "bench_high_priority_queue_depth", + "Benchmark high priority queue depth", +); + +const BENCH_NORMAL_PRIORITY_QUEUE_DEPTH: MetricGauge = MetricGauge::new( + MetricScope::Infra, + "bench_normal_priority_queue_depth", + "Benchmark normal priority queue depth", +); + +// Label permutations for histograms +const BENCH_LABEL_PERMUTATIONS: &[&[(&str, &str)]] = + &[&[("request_type", "add_tx")], &[("request_type", "get_txs")]]; + +const BENCH_PROCESSING_TIMES: LabeledMetricHistogram = LabeledMetricHistogram::new( + MetricScope::Infra, + "bench_processing_times", + "Benchmark processing times", + BENCH_LABEL_PERMUTATIONS, +); + +const BENCH_QUEUEING_TIMES: LabeledMetricHistogram = LabeledMetricHistogram::new( + MetricScope::Infra, + "bench_queueing_times", + "Benchmark queueing times", + BENCH_LABEL_PERMUTATIONS, +); + +const BENCH_RESPONSE_TIMES: LabeledMetricHistogram = LabeledMetricHistogram::new( + MetricScope::Infra, + "bench_response_times", + "Benchmark response times", + BENCH_LABEL_PERMUTATIONS, +); + +// Static metrics instances for benchmark +const BENCH_LOCAL_SERVER_METRICS: LocalServerMetrics = LocalServerMetrics::new( + &BENCH_MSGS_RECEIVED, + &BENCH_MSGS_PROCESSED, + &BENCH_HIGH_PRIORITY_QUEUE_DEPTH, + &BENCH_NORMAL_PRIORITY_QUEUE_DEPTH, + &BENCH_PROCESSING_TIMES, + &BENCH_QUEUEING_TIMES, +); + +const BENCH_LOCAL_CLIENT_METRICS: LocalClientMetrics = + LocalClientMetrics::new(&BENCH_RESPONSE_TIMES); + +struct TransactionGenerator { + multi_tx_generator: MultiAccountTransactionGenerator, + sender_address: ContractAddress, +} + +impl TransactionGenerator { + fn new(cairo_version: CairoVersion) -> Self { + let mut multi_tx_generator = MultiAccountTransactionGenerator::new(); + let account_type = FeatureContract::AccountWithoutValidations(cairo_version); + multi_tx_generator.register_deployed_account(account_type); + let sender_address = multi_tx_generator.account_with_id(0).sender_address(); + Self { multi_tx_generator, sender_address } + } + + fn generate_invoke(&mut self, index: usize) -> AddTransactionArgs { + let RpcTransaction::Invoke(invoke_tx) = + self.multi_tx_generator.account_with_id_mut(0).generate_trivial_rpc_invoke_tx(0) + else { + panic!("Expected RpcTransaction::Invoke") + }; + + AddTransactionArgs { + tx: InternalRpcTransaction { + tx: InternalRpcTransactionWithoutTxHash::Invoke(invoke_tx), + tx_hash: tx_hash!(index + 100), // Use index to create a unique hash + }, + // Since generate_invoke_with_tip() creates first transaction with nonce 1 (first + // invoke after deploy), the AccountState must also have nonce 1 to initialize the + // mempool nonce management to 1. + account_state: AccountState { address: self.sender_address, nonce: nonce!(1) }, + } + } +} + +#[derive(Clone)] +pub struct BenchTestSetupConfig { + pub n_txs: usize, + pub mempool_config: MempoolConfig, + pub chunk_size: usize, // Number of "add_tx" requests per one "get_tx" request. +} + +pub struct BenchTestSetup { + config: BenchTestSetupConfig, + txs: Vec, +} + +/// Server-Client setup for realistic benchmarking +pub struct MempoolServerClientSetup { + pub client: SharedMempoolClient, + _server_handle: tokio::task::JoinHandle<()>, +} + +impl BenchTestSetup { + pub fn new(config: &BenchTestSetupConfig) -> Self { + let cairo_version = CairoVersion::Cairo1(RunnableCairo1::Casm); + let mut tx_generator = TransactionGenerator::new(cairo_version); + + let txs = (0..config.n_txs).map(|i| tx_generator.generate_invoke(i)).collect(); + + Self { config: config.clone(), txs } + } + + /// Creates a server-client setup for realistic benchmarking + /// This simulates how mempool is accessed in the real application + pub async fn create_server_client_setup(&self) -> MempoolServerClientSetup { + // Create server configuration + let server_config = LocalServerConfig::default(); + + // Create communication channels + let (tx, rx) = mpsc::channel(server_config.inbound_requests_channel_capacity); + + // Create minimal overhead P2P client for benchmark + let bench_p2p_client = Arc::new(BenchMempoolP2pPropagator); + + // Create mock config manager client for benchmark + let mut mock_config_manager = MockConfigManagerClient::new(); + let dynamic_config = self.config.mempool_config.dynamic_config.clone(); + mock_config_manager + .expect_get_mempool_dynamic_config() + .returning(move || Ok(dynamic_config.clone())); + let mock_config_manager = Arc::new(mock_config_manager); + + // Create the mempool component + let mempool_component = create_mempool( + self.config.mempool_config.clone(), + bench_p2p_client, + mock_config_manager, + ); + + // Use static metrics for benchmark + let server_metrics = &BENCH_LOCAL_SERVER_METRICS; + let client_metrics = &BENCH_LOCAL_CLIENT_METRICS; + + // Create the server + let server = LocalMempoolServer::new(mempool_component, &server_config, rx, server_metrics); + + // Start the server in a background task + // Note: LocalComponentServer::start() will panic when it finishes processing + // all requests. This is the expected behavior and not an error. + let server_handle = tokio::spawn(async move { + let mut server = server; + let _ = server.start().await; // Expected panic when server finishes + }); + + // Create the client + let client = LocalMempoolClient::new(tx, client_metrics); + let shared_client: SharedMempoolClient = Arc::new(client); + + // Give the server a moment to fully start + tokio::task::yield_now().await; + + MempoolServerClientSetup { client: shared_client, _server_handle: server_handle } + } + + /// Task that continuously adds transactions to the mempool via client. + /// Simulates concurrent producers in a real system + async fn add_tx_task(client: SharedMempoolClient, txs: Vec) { + for tx in txs.into_iter() { + let wrapped_args = AddTransactionArgsWrapper { args: tx, p2p_message_metadata: None }; + + client + .add_tx(wrapped_args) + .await + .unwrap_or_else(|e| panic!("Failed to add tx to mempool: {e:?}")); + } + } + + /// Task that continuously retrieves transactions from the mempool via client. + /// Simulates concurrent consumers in a real system + async fn get_txs_task(client: SharedMempoolClient, n_txs: usize, chunk_size: usize) { + let mut n_txs_received = 0; + let mut last_committed_chunk = 0; + let mut address_to_nonce = HashMap::::new(); + + while n_txs_received < n_txs { + let retrieved_txs = client + .get_txs(chunk_size) + .await + .unwrap_or_else(|e| panic!("Failed to get txs from mempool: {e:?}")); + + n_txs_received += retrieved_txs.len(); + + // Aggregate the highest nonce for each contract address in this chunk. + retrieved_txs.iter().for_each(|tx| { + let address = tx.contract_address(); + let nonce = tx.nonce(); + address_to_nonce + .entry(address) + .and_modify(|max_nonce| *max_nonce = (*max_nonce).max(nonce)) + .or_insert(nonce); + }); + + // Commit a block when we've accumulated enough transactions for a complete chunk. + // This simulates the block production cycle where transactions are periodically + // committed after being retrieved from the mempool. + if last_committed_chunk < n_txs_received / chunk_size { + last_committed_chunk += 1; + + client + .commit_block(CommitBlockArgs { + address_to_nonce: std::mem::take(&mut address_to_nonce), + rejected_tx_hashes: IndexSet::new(), + }) + .await + .unwrap(); + } + + // If no txs retrieved, wait a bit for add_tx_task to add more + if retrieved_txs.is_empty() { + tokio::time::sleep(tokio::time::Duration::from_micros(100)).await; + } + } + } + + /// Concurrent benchmark using server-client architecture + /// This simulates realistic concurrent access patterns to the mempool + pub async fn mempool_add_get_txs(&self) { + // Create server-client setup for realistic benchmarking + let server_client_setup = self.create_server_client_setup().await; + let client = Arc::clone(&server_client_setup.client); + + // Create tasks for concurrent execution + let add_task = tokio::spawn(Self::add_tx_task(Arc::clone(&client), self.txs.clone())); + let get_task = tokio::spawn(Self::get_txs_task( + Arc::clone(&client), + self.config.n_txs, + self.config.chunk_size, + )); + + // Wait for both tasks to complete + // Using try_join! for better error propagation in benchmarks + tokio::try_join!(add_task, get_task) + .expect("One or both tasks failed during benchmark execution"); + } +} diff --git a/crates/apollo_mempool/src/mempool.rs b/crates/apollo_mempool/src/mempool.rs index c0071cfaf04..c297edcefe5 100644 --- a/crates/apollo_mempool/src/mempool.rs +++ b/crates/apollo_mempool/src/mempool.rs @@ -16,7 +16,11 @@ use indexmap::IndexSet; use rand::{thread_rng, Rng}; use starknet_api::block::GasPrice; use starknet_api::core::{ContractAddress, Nonce}; -use starknet_api::rpc_transaction::{InternalRpcTransaction, InternalRpcTransactionWithoutTxHash}; +use starknet_api::rpc_transaction::{ + InternalRpcTransaction, + InternalRpcTransactionLabelValue, + InternalRpcTransactionWithoutTxHash, +}; use starknet_api::transaction::fields::Tip; use starknet_api::transaction::TransactionHash; use tracing::{debug, info, instrument, trace}; @@ -27,12 +31,13 @@ use crate::metrics::{ metric_count_expired_txs, metric_count_rejected_txs, metric_set_get_txs_size, - MempoolMetricHandle, + LABEL_NAME_TX_TYPE, MEMPOOL_DELAYED_DECLARES_SIZE, MEMPOOL_PENDING_QUEUE_SIZE, MEMPOOL_POOL_SIZE, MEMPOOL_PRIORITY_QUEUE_SIZE, MEMPOOL_TOTAL_SIZE_BYTES, + MEMPOOL_TRANSACTIONS_RECEIVED, TRANSACTION_TIME_SPENT_UNTIL_BATCHED, }; use crate::transaction_pool::TransactionPool; @@ -343,9 +348,6 @@ impl Mempool { err )] pub fn add_tx(&mut self, args: AddTransactionArgs) -> MempoolResult<()> { - let mut metric_handle = MempoolMetricHandle::new(&args.tx.tx); - metric_handle.count_transaction_received(); - // First remove old transactions from the pool. let mut account_nonce_updates = self.remove_expired_txs(); self.add_ready_declares(); @@ -358,7 +360,10 @@ impl Mempool { self.handle_capacity_overflow(&args.tx, args.account_state.nonce)?; } - metric_handle.transaction_inserted(); + MEMPOOL_TRANSACTIONS_RECEIVED.increment( + 1, + &[(LABEL_NAME_TX_TYPE, InternalRpcTransactionLabelValue::from(&args.tx.tx).into())], + ); // May override a removed queued nonce with the received account nonce or the account's // state nonce. @@ -558,7 +563,7 @@ impl Mempool { Ok(()) } - /// If this transaction is already in the pool but the fees have increased beyond the thereshold + /// If this transaction is already in the pool but the fees have increased beyond the threshold /// in the config, remove the existing transaction from the queue and the pool. /// Note: This method will **not** add the new incoming transaction. #[instrument(level = "debug", skip(self, incoming_tx), err)] @@ -583,9 +588,9 @@ impl Mempool { }; if !self.should_replace_tx(&existing_tx_reference, &incoming_tx_reference) { - debug!( - "{existing_tx_reference} was not replaced by {incoming_tx_reference} due to - insufficient fee escalation." + info!( + "{existing_tx_reference} was not replaced by {incoming_tx_reference} due to \ + insufficient fee escalation." ); // TODO(Elin): consider adding a more specific error type / message. return Err(MempoolError::DuplicateNonce { address, nonce }); @@ -633,13 +638,24 @@ impl Mempool { incoming_value >= escalation_qualified_value } + #[instrument(skip_all, parent = None)] + fn log_and_count_expired_txs(&self, expired_txs: &[TransactionReference]) { + if !expired_txs.is_empty() { + metric_count_expired_txs(expired_txs.len()); + info!( + "Removed expired transactions: {:?}", + expired_txs.iter().map(|tx| tx.tx_hash).collect::>() + ); + } + } + fn remove_expired_txs(&mut self) -> AddressToNonce { let removed_txs = self .tx_pool .remove_txs_older_than(self.config.dynamic_config.transaction_ttl, &self.state.staged); let queued_txs = self.tx_queue.remove_txs(&removed_txs); - metric_count_expired_txs(removed_txs.len()); + self.log_and_count_expired_txs(&removed_txs); self.update_state_metrics(); queued_txs .into_iter() @@ -666,7 +682,7 @@ impl Mempool { }); // Remove old transactions from the pool. - metric_count_expired_txs(old_txs.len()); + self.log_and_count_expired_txs(&old_txs); let account_nonce_updates: AddressToNonce = old_txs .into_iter() .map(|tx| { @@ -848,8 +864,8 @@ impl std::fmt::Display for TransactionReference { let TransactionReference { address, nonce, tx_hash, tip, max_l2_gas_price } = self; write!( f, - "TransactionReference {{ address: {address}, nonce: {nonce}, tx_hash: {tx_hash}, - tip: {tip}, max_l2_gas_price: {max_l2_gas_price} }}" + "TransactionReference {{ address: {address}, nonce: {nonce}, tx_hash: {tx_hash}, tip: \ + {tip}, max_l2_gas_price: {max_l2_gas_price} }}" ) } } diff --git a/crates/apollo_mempool/src/mempool_test.rs b/crates/apollo_mempool/src/mempool_test.rs index 533e5ba1f39..b80b16a6189 100644 --- a/crates/apollo_mempool/src/mempool_test.rs +++ b/crates/apollo_mempool/src/mempool_test.rs @@ -1270,12 +1270,11 @@ fn metrics_correctness() { commit_block(&mut mempool, [("0x9", 1)], []); let expected_metrics = MempoolMetrics { - txs_received_invoke: 9, + txs_received_invoke: 8, txs_received_declare: 2, txs_received_deploy_account: 0, txs_committed: 2, txs_dropped_expired: 1, - txs_dropped_failed_add_tx_checks: 1, txs_dropped_rejected: 1, txs_dropped_evicted: 1, pool_size: 4, diff --git a/crates/apollo_mempool/src/metrics.rs b/crates/apollo_mempool/src/metrics.rs index 73d198da2b4..b2367adad05 100644 --- a/crates/apollo_mempool/src/metrics.rs +++ b/crates/apollo_mempool/src/metrics.rs @@ -7,10 +7,7 @@ use apollo_infra::metrics::{ }; use apollo_mempool_types::mempool_types::MEMPOOL_REQUEST_LABELS; use apollo_metrics::{define_infra_metrics, define_metrics, generate_permutation_labels}; -use starknet_api::rpc_transaction::{ - InternalRpcTransactionLabelValue, - InternalRpcTransactionWithoutTxHash, -}; +use starknet_api::rpc_transaction::InternalRpcTransactionLabelValue; use strum::{EnumVariantNames, VariantNames}; use strum_macros::{EnumIter, IntoStaticStr}; @@ -45,54 +42,14 @@ generate_permutation_labels! { (LABEL_NAME_DROP_REASON, DropReason), } -enum TransactionStatus { - AddedToMempool, - Dropped, -} - #[derive(IntoStaticStr, EnumIter, EnumVariantNames)] #[strum(serialize_all = "snake_case")] pub enum DropReason { - FailedAddTxChecks, Expired, Rejected, Evicted, } -pub(crate) struct MempoolMetricHandle { - tx_type: InternalRpcTransactionLabelValue, - tx_status: TransactionStatus, -} - -impl MempoolMetricHandle { - pub fn new(tx: &InternalRpcTransactionWithoutTxHash) -> Self { - let tx_type = InternalRpcTransactionLabelValue::from(tx); - Self { tx_type, tx_status: TransactionStatus::Dropped } - } - - fn label(&self) -> Vec<(&'static str, &'static str)> { - vec![(LABEL_NAME_TX_TYPE, self.tx_type.into())] - } - - pub fn count_transaction_received(&self) { - MEMPOOL_TRANSACTIONS_RECEIVED.increment(1, &self.label()); - } - - pub fn transaction_inserted(&mut self) { - self.tx_status = TransactionStatus::AddedToMempool; - } -} - -impl Drop for MempoolMetricHandle { - fn drop(&mut self) { - match self.tx_status { - TransactionStatus::Dropped => MEMPOOL_TRANSACTIONS_DROPPED - .increment(1, &[(LABEL_NAME_DROP_REASON, DropReason::FailedAddTxChecks.into())]), - TransactionStatus::AddedToMempool => {} - } - } -} - pub(crate) fn metric_count_expired_txs(n_txs: usize) { MEMPOOL_TRANSACTIONS_DROPPED.increment( n_txs.try_into().expect("The number of expired_txs should fit u64"), diff --git a/crates/apollo_mempool/src/test_utils.rs b/crates/apollo_mempool/src/test_utils.rs index d16e803c3e7..c993eb79272 100644 --- a/crates/apollo_mempool/src/test_utils.rs +++ b/crates/apollo_mempool/src/test_utils.rs @@ -287,7 +287,6 @@ pub struct MempoolMetrics { pub txs_received_deploy_account: u64, pub txs_committed: u64, pub txs_dropped_expired: u64, - pub txs_dropped_failed_add_tx_checks: u64, pub txs_dropped_rejected: u64, pub txs_dropped_evicted: u64, pub pool_size: u64, @@ -325,11 +324,6 @@ impl MempoolMetrics { self.txs_dropped_expired, &[(LABEL_NAME_DROP_REASON, DropReason::Expired.into())], ); - MEMPOOL_TRANSACTIONS_DROPPED.assert_eq( - metrics, - self.txs_dropped_failed_add_tx_checks, - &[(LABEL_NAME_DROP_REASON, DropReason::FailedAddTxChecks.into())], - ); MEMPOOL_TRANSACTIONS_DROPPED.assert_eq( metrics, self.txs_dropped_rejected, diff --git a/crates/apollo_mempool_p2p/src/propagator/mod.rs b/crates/apollo_mempool_p2p/src/propagator/mod.rs index 2b409c7cdf5..01e5429956e 100644 --- a/crates/apollo_mempool_p2p/src/propagator/mod.rs +++ b/crates/apollo_mempool_p2p/src/propagator/mod.rs @@ -16,7 +16,7 @@ use apollo_network_types::network_types::BroadcastedMessageMetadata; use apollo_protobuf::mempool::RpcTransactionBatch; use async_trait::async_trait; use starknet_api::rpc_transaction::{InternalRpcTransaction, RpcTransaction}; -use tracing::{debug, warn}; +use tracing::{debug, trace, warn}; use crate::metrics::MEMPOOL_P2P_BROADCASTED_BATCH_SIZE; @@ -63,7 +63,7 @@ impl ComponentRequestHandler { - debug!("Received a request to broadcast queued transactions, broadcasting."); + trace!("Received a request to broadcast queued transactions, broadcasting."); MempoolP2pPropagatorResponse::BroadcastQueuedTransactions( self.broadcast_queued_transactions().await, ) diff --git a/crates/apollo_metrics/src/lib.rs b/crates/apollo_metrics/src/lib.rs index 84feec03d39..3782ee2392a 100644 --- a/crates/apollo_metrics/src/lib.rs +++ b/crates/apollo_metrics/src/lib.rs @@ -4,3 +4,5 @@ pub mod metrics; // Its being exported here to be used in define_metrics macro. pub use paste; + +pub use crate::metrics::MetricCommon; diff --git a/crates/apollo_metrics/src/metrics.rs b/crates/apollo_metrics/src/metrics.rs index e0104fdffac..376d8e82670 100644 --- a/crates/apollo_metrics/src/metrics.rs +++ b/crates/apollo_metrics/src/metrics.rs @@ -50,46 +50,66 @@ pub enum MetricScope { Tokio, } -pub struct MetricCounter { +#[derive(Clone, Debug)] +struct Metric { scope: MetricScope, name: &'static str, description: &'static str, - initial_value: u64, } -impl MetricCounter { - pub const fn new( - scope: MetricScope, - name: &'static str, - description: &'static str, - initial_value: u64, - ) -> Self { - Self { scope, name, description, initial_value } +impl Metric { + pub const fn new(scope: MetricScope, name: &'static str, description: &'static str) -> Self { + Self { scope, name, description } } +} - pub const fn get_name(&self) -> &'static str { +pub trait MetricCommon { + fn get_name(&self) -> &'static str; + fn get_name_with_filter(&self) -> String; + fn get_scope(&self) -> MetricScope; + fn get_description(&self) -> &'static str; +} + +impl MetricCommon for Metric { + fn get_name(&self) -> &'static str { self.name } - pub fn get_name_with_filter(&self) -> String { + fn get_name_with_filter(&self) -> String { format!("{}{}", self.name, metric_label_filter!()) } - pub const fn get_scope(&self) -> MetricScope { + fn get_scope(&self) -> MetricScope { self.scope } - pub const fn get_description(&self) -> &'static str { + fn get_description(&self) -> &'static str { self.description } +} + +pub struct MetricCounter { + metric: Metric, + initial_value: u64, +} + +impl MetricCounter { + pub const fn new( + scope: MetricScope, + name: &'static str, + description: &'static str, + initial_value: u64, + ) -> Self { + Self { metric: Metric::new(scope, name, description), initial_value } + } pub fn register(&self) { - counter!(self.name).absolute(self.initial_value); - describe_counter!(self.name, self.description); + counter!(self.get_name()).absolute(self.initial_value); + describe_counter!(self.get_name(), self.get_description()); } pub fn increment(&self, value: u64) { - counter!(self.name).increment(value); + counter!(self.get_name()).increment(value); } #[cfg(any(feature = "testing", test))] @@ -110,10 +130,26 @@ impl MetricCounter { } } +impl MetricCommon for MetricCounter { + fn get_name(&self) -> &'static str { + self.metric.get_name() + } + + fn get_name_with_filter(&self) -> String { + format!("{}{}", self.metric.get_name(), metric_label_filter!()) + } + + fn get_scope(&self) -> MetricScope { + self.metric.get_scope() + } + + fn get_description(&self) -> &'static str { + self.metric.get_description() + } +} + pub struct LabeledMetricCounter { - scope: MetricScope, - name: &'static str, - description: &'static str, + metric: Metric, initial_value: u64, label_permutations: &'static [&'static [(&'static str, &'static str)]], } @@ -126,34 +162,18 @@ impl LabeledMetricCounter { initial_value: u64, label_permutations: &'static [&'static [(&'static str, &'static str)]], ) -> Self { - Self { scope, name, description, initial_value, label_permutations } - } - - pub const fn get_name(&self) -> &'static str { - self.name - } - - pub fn get_name_with_filter(&self) -> String { - format!("{}{}", self.name, metric_label_filter!()) - } - - pub const fn get_scope(&self) -> MetricScope { - self.scope - } - - pub const fn get_description(&self) -> &'static str { - self.description + Self { metric: Metric::new(scope, name, description), initial_value, label_permutations } } pub fn register(&self) { self.label_permutations.iter().map(|&slice| slice.to_vec()).for_each(|labels| { - counter!(self.name, &labels).absolute(self.initial_value); + counter!(self.get_name(), &labels).absolute(self.initial_value); }); - describe_counter!(self.name, self.description); + describe_counter!(self.get_name(), self.get_description()); } pub fn increment(&self, value: u64, labels: &[(&'static str, &'static str)]) { - counter!(self.name, labels).increment(value); + counter!(self.get_name(), labels).increment(value); } #[cfg(any(feature = "testing", test))] @@ -178,44 +198,44 @@ impl LabeledMetricCounter { } } -pub struct MetricGauge { - scope: MetricScope, - name: &'static str, - description: &'static str, -} - -impl MetricGauge { - pub const fn new(scope: MetricScope, name: &'static str, description: &'static str) -> Self { - Self { scope, name, description } +impl MetricCommon for LabeledMetricCounter { + fn get_name(&self) -> &'static str { + self.metric.get_name() } - pub const fn get_name(&self) -> &'static str { - self.name + fn get_name_with_filter(&self) -> String { + format!("{}{}", self.metric.get_name(), metric_label_filter!()) } - pub fn get_name_with_filter(&self) -> String { - format!("{}{}", self.name, metric_label_filter!()) + fn get_scope(&self) -> MetricScope { + self.metric.get_scope() } - pub const fn get_scope(&self) -> MetricScope { - self.scope + fn get_description(&self) -> &'static str { + self.metric.get_description() } +} - pub const fn get_description(&self) -> &'static str { - self.description +pub struct MetricGauge { + metric: Metric, +} + +impl MetricGauge { + pub const fn new(scope: MetricScope, name: &'static str, description: &'static str) -> Self { + Self { metric: Metric::new(scope, name, description) } } pub fn register(&self) { - let _ = gauge!(self.name); - describe_gauge!(self.name, self.description); + let _ = gauge!(self.get_name()); + describe_gauge!(self.get_name(), self.get_description()); } pub fn increment(&self, value: T) { - gauge!(self.name).increment(value.into_f64()); + gauge!(self.get_name()).increment(value.into_f64()); } pub fn decrement(&self, value: T) { - gauge!(self.name).decrement(value.into_f64()); + gauge!(self.get_name()).decrement(value.into_f64()); } #[cfg(any(feature = "testing", test))] @@ -224,11 +244,11 @@ impl MetricGauge { } pub fn set(&self, value: T) { - gauge!(self.name).set(value.into_f64()); + gauge!(self.get_name()).set(value.into_f64()); } pub fn set_lossy(&self, value: T) { - gauge!(self.name).set(value.into_f64()); + gauge!(self.get_name()).set(value.into_f64()); } #[cfg(any(feature = "testing", test))] @@ -244,6 +264,23 @@ impl MetricGauge { } } +impl MetricCommon for MetricGauge { + fn get_name(&self) -> &'static str { + self.metric.get_name() + } + + fn get_name_with_filter(&self) -> String { + format!("{}{}", self.metric.get_name(), metric_label_filter!()) + } + + fn get_scope(&self) -> MetricScope { + self.metric.get_scope() + } + + fn get_description(&self) -> &'static str { + self.metric.get_description() + } +} /// An object which can be lossy converted into a `f64` representation. pub trait LossyIntoF64 { fn into_f64(self) -> f64; @@ -270,9 +307,7 @@ macro_rules! into_f64 { into_f64!(u64, usize, i64, u128); pub struct LabeledMetricGauge { - scope: MetricScope, - name: &'static str, - description: &'static str, + metric: Metric, label_permutations: &'static [&'static [(&'static str, &'static str)]], } @@ -283,38 +318,22 @@ impl LabeledMetricGauge { description: &'static str, label_permutations: &'static [&'static [(&'static str, &'static str)]], ) -> Self { - Self { scope, name, description, label_permutations } - } - - pub const fn get_name(&self) -> &'static str { - self.name - } - - pub fn get_name_with_filter(&self) -> String { - format!("{}{}", self.name, metric_label_filter!()) - } - - pub const fn get_scope(&self) -> MetricScope { - self.scope - } - - pub const fn get_description(&self) -> &'static str { - self.description + Self { metric: Metric::new(scope, name, description), label_permutations } } pub fn register(&self) { self.label_permutations.iter().map(|&slice| slice.to_vec()).for_each(|label| { - let _ = gauge!(self.name, &label); + let _ = gauge!(self.get_name(), &label); }); - describe_gauge!(self.name, self.description); + describe_gauge!(self.get_name(), self.get_description()); } pub fn increment(&self, value: T, label: &[(&'static str, &'static str)]) { - gauge!(self.name, label).increment(value); + gauge!(self.get_name(), label).increment(value); } pub fn decrement(&self, value: T, label: &[(&'static str, &'static str)]) { - gauge!(self.name, label).decrement(value.into_f64()); + gauge!(self.get_name(), label).decrement(value.into_f64()); } #[cfg(any(feature = "testing", test))] @@ -327,7 +346,7 @@ impl LabeledMetricGauge { } pub fn set(&self, value: T, label: &[(&'static str, &'static str)]) { - gauge!(self.name, label).set(value.into_f64()); + gauge!(self.get_name(), label).set(value.into_f64()); } #[cfg(any(feature = "testing", test))] @@ -343,11 +362,27 @@ impl LabeledMetricGauge { } } +impl MetricCommon for LabeledMetricGauge { + fn get_name(&self) -> &'static str { + self.metric.get_name() + } + + fn get_name_with_filter(&self) -> String { + format!("{}{}", self.metric.get_name(), metric_label_filter!()) + } + + fn get_scope(&self) -> MetricScope { + self.metric.get_scope() + } + + fn get_description(&self) -> &'static str { + self.metric.get_description() + } +} + #[derive(Clone)] pub struct MetricHistogram { - scope: MetricScope, - name: &'static str, - description: &'static str, + metric: Metric, } #[derive(Default, Debug)] @@ -365,48 +400,32 @@ impl PartialEq for HistogramValue { impl MetricHistogram { pub const fn new(scope: MetricScope, name: &'static str, description: &'static str) -> Self { - Self { scope, name, description } - } - - pub const fn get_name(&self) -> &'static str { - self.name - } - - pub fn get_name_with_filter(&self) -> String { - format!("{}_bucket{}", self.name, metric_label_filter!()) + Self { metric: Metric::new(scope, name, description) } } pub fn get_name_sum_with_filter(&self) -> String { - format!("{}_sum{}", self.name, metric_label_filter!()) + format!("{}_sum{}", self.get_name(), metric_label_filter!()) } pub fn get_name_count_with_filter(&self) -> String { - format!("{}_count{}", self.name, metric_label_filter!()) - } - - pub const fn get_scope(&self) -> MetricScope { - self.scope - } - - pub const fn get_description(&self) -> &'static str { - self.description + format!("{}_count{}", self.get_name(), metric_label_filter!()) } pub fn register(&self) { - let _ = histogram!(self.name); - describe_histogram!(self.name, self.description); + let _ = histogram!(self.get_name()); + describe_histogram!(self.get_name(), self.get_description()); } pub fn record(&self, value: T) { - histogram!(self.name).record(value.into_f64()); + histogram!(self.get_name()).record(value.into_f64()); } pub fn record_lossy(&self, value: T) { - histogram!(self.name).record(value.into_f64()); + histogram!(self.get_name()).record(value.into_f64()); } pub fn record_many(&self, value: T, count: usize) { - histogram!(self.name).record_many(value.into_f64(), count); + histogram!(self.get_name()).record_many(value.into_f64(), count); } #[cfg(any(feature = "testing", test))] @@ -422,10 +441,26 @@ impl MetricHistogram { } } +impl MetricCommon for MetricHistogram { + fn get_name(&self) -> &'static str { + self.metric.get_name() + } + + fn get_name_with_filter(&self) -> String { + format!("{}_bucket{}", self.metric.get_name(), metric_label_filter!()) + } + + fn get_scope(&self) -> MetricScope { + self.metric.get_scope() + } + + fn get_description(&self) -> &'static str { + self.metric.get_description() + } +} + pub struct LabeledMetricHistogram { - scope: MetricScope, - name: &'static str, - description: &'static str, + metric: Metric, label_permutations: &'static [&'static [(&'static str, &'static str)]], } @@ -436,23 +471,7 @@ impl LabeledMetricHistogram { description: &'static str, label_permutations: &'static [&'static [(&'static str, &'static str)]], ) -> Self { - Self { scope, name, description, label_permutations } - } - - pub const fn get_name(&self) -> &'static str { - self.name - } - - pub fn get_name_with_filter(&self) -> String { - format!("{}_bucket{}", self.name, metric_label_filter!()) - } - - pub const fn get_scope(&self) -> MetricScope { - self.scope - } - - pub const fn get_description(&self) -> &'static str { - self.description + Self { metric: Metric::new(scope, name, description), label_permutations } } // Returns a flattened and sorted list of the unique label values across all label permutations. @@ -470,13 +489,18 @@ impl LabeledMetricHistogram { pub fn register(&self) { self.label_permutations.iter().map(|&slice| slice.to_vec()).for_each(|labels| { - let _ = histogram!(self.name, &labels); + let _ = histogram!(self.get_name(), &labels); }); - describe_histogram!(self.name, self.description); + describe_histogram!(self.get_name(), self.get_description()); + } + + /// Returns the label name used by this labeled histogram. + pub fn get_label_name(&self) -> &'static str { + self.label_permutations[0][0].0 } pub fn record(&self, value: T, labels: &[(&'static str, &'static str)]) { - histogram!(self.name, labels).record(value.into_f64()); + histogram!(self.get_name(), labels).record(value.into_f64()); } pub fn record_many( @@ -485,7 +509,7 @@ impl LabeledMetricHistogram { count: usize, labels: &[(&'static str, &'static str)], ) { - histogram!(self.name, labels).record_many(value.into_f64(), count); + histogram!(self.get_name(), labels).record_many(value.into_f64(), count); } #[cfg(any(feature = "testing", test))] @@ -510,6 +534,24 @@ impl LabeledMetricHistogram { } } +impl MetricCommon for LabeledMetricHistogram { + fn get_name(&self) -> &'static str { + self.metric.get_name() + } + + fn get_name_with_filter(&self) -> String { + format!("{}_bucket{}", self.metric.get_name(), metric_label_filter!()) + } + + fn get_scope(&self) -> MetricScope { + self.metric.get_scope() + } + + fn get_description(&self) -> &'static str { + self.metric.get_description() + } +} + /// Parses a specific numeric metric value from a metrics string. /// /// # Arguments diff --git a/crates/apollo_network/src/lib.rs b/crates/apollo_network/src/lib.rs index 08b79067cda..359c1f12b67 100644 --- a/crates/apollo_network/src/lib.rs +++ b/crates/apollo_network/src/lib.rs @@ -19,12 +19,13 @@ mod test_utils; pub mod utils; use std::collections::{BTreeMap, HashSet}; -use std::str::FromStr; use std::time::Duration; use apollo_config::converters::{ + deserialize_comma_separated_str, deserialize_optional_vec_u8, deserialize_seconds_to_duration, + serialize_optional_comma_separated, serialize_optional_vec_u8, }; use apollo_config::dumping::{ @@ -39,54 +40,12 @@ use discovery::DiscoveryConfig; use libp2p::swarm::dial_opts::DialOpts; use libp2p::Multiaddr; use peer_manager::PeerManagerConfig; -use serde::de::Error; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use starknet_api::core::ChainId; use validator::{Validate, ValidationError}; pub(crate) type Bytes = Vec; -// TODO(AndrewL): Fix this -/// This function considers `""` to be `None` and -/// `"multiaddr1,multiaddr2"` to be `Some(vec![multiaddr1, multiaddr2])`. -/// It was purposefully designed this way to be compatible with the old config where only one -/// bootstrap peer was supported. Hence there is no way to express an empty vector in the config. -fn deserialize_multi_addrs<'de, D>(de: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let raw_str: String = Deserialize::deserialize(de).unwrap_or_default(); - if raw_str.is_empty() { - return Ok(None); - } - - let mut vector = Vec::new(); - for i in raw_str.split(',').filter(|s| !s.is_empty()) { - let value = Multiaddr::from_str(i).map_err(|_| { - D::Error::custom(format!("Couldn't deserialize vector. Failed to parse value: {i}")) - })?; - vector.push(value); - } - - if vector.is_empty() { - return Ok(None); - } - - Ok(Some(vector)) -} - -// TODO(Tsabary): move to the config converter module. -pub fn serialize_multi_addrs(multi_addrs: &Option>) -> String { - match multi_addrs { - None => "".to_owned(), - Some(multi_addrs) => multi_addrs - .iter() - .map(|multiaddr| multiaddr.to_string()) - .collect::>() - .join(","), - } -} - // TODO(Shahak): add peer manager config to the network config #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Validate)] pub struct NetworkConfig { @@ -95,7 +54,7 @@ pub struct NetworkConfig { pub session_timeout: Duration, #[serde(deserialize_with = "deserialize_seconds_to_duration")] pub idle_connection_timeout: Duration, - #[serde(deserialize_with = "deserialize_multi_addrs")] + #[serde(deserialize_with = "deserialize_comma_separated_str")] #[validate(custom(function = "validate_bootstrap_peer_multiaddr_list"))] pub bootstrap_peer_multiaddr: Option>, #[validate(custom = "validate_vec_u256")] @@ -153,11 +112,7 @@ impl SerializeConfig for NetworkConfig { // TODO(Tsabary): this is not the proper way to dump a config. Needs fixing, and // specifically, need to move the condition to be part of the serialization fn. config.extend(ser_optional_param( - &if self.bootstrap_peer_multiaddr.is_some(){ - Some(serialize_multi_addrs(&self.bootstrap_peer_multiaddr)) - } else { - None - }, + &serialize_optional_comma_separated(&self.bootstrap_peer_multiaddr), String::from(""), "bootstrap_peer_multiaddr", "The multiaddress of the peer node. It should include the peer's id. For more info: https://docs.libp2p.io/concepts/fundamentals/peers/", diff --git a/crates/apollo_node/resources/config_schema.json b/crates/apollo_node/resources/config_schema.json index 7c5fb58bd72..b3aa2bae87f 100644 --- a/crates/apollo_node/resources/config_schema.json +++ b/crates/apollo_node/resources/config_schema.json @@ -57,7 +57,7 @@ "batcher_config.block_builder_config.bouncer_config.block_max_capacity.sierra_gas": { "description": "An upper bound on the total sierra_gas used in a block.", "privacy": "Public", - "value": 5000000000 + "value": 6000000000 }, "batcher_config.block_builder_config.bouncer_config.block_max_capacity.state_diff_size": { "description": "An upper bound on the total state diff size in a block.", @@ -144,6 +144,11 @@ "privacy": "Public", "value": 100 }, + "batcher_config.block_builder_config.proposer_idle_detection_delay_millis": { + "description": "Minimum time (in milliseconds) that must pass since block creation started before checking for idle state. If this delay has passed AND no transactions are currently being executed, the proposer will finish building the current block.", + "privacy": "Public", + "value": 2000 + }, "batcher_config.block_builder_config.tx_polling_interval_millis": { "description": "Time to wait (in milliseconds) between transaction requests when the previous request returned no transactions.", "privacy": "Public", @@ -155,7 +160,7 @@ "privacy": "Public" }, "batcher_config.block_builder_config.versioned_constants_overrides.max_n_events": { - "description": "Maximum number of events that can be emitted from the transation.", + "description": "Maximum number of events that can be emitted from the transaction.", "pointer_target": "versioned_constants_overrides.max_n_events", "privacy": "Public" }, @@ -274,6 +279,11 @@ "pointer_target": "recorder_url", "privacy": "Private" }, + "batcher_config.propose_l1_txs_every": { + "description": "Only propose L1 transactions every N proposals.", + "privacy": "Public", + "value": 1 + }, "batcher_config.storage.db_config.chain_id": { "description": "The chain to follow. For more details see https://docs.starknet.io/documentation/architecture_and_concepts/Blocks/transactions/#chain-id.", "pointer_target": "chain_id", @@ -1539,22 +1549,22 @@ "privacy": "Public", "value": 5 }, - "consensus_manager_config.consensus_manager_config.static_config.sync_retry_interval": { + "consensus_manager_config.consensus_manager_config.dynamic_config.sync_retry_interval": { "description": "The duration (seconds) between sync attempts.", "privacy": "Public", "value": 1.0 }, - "consensus_manager_config.consensus_manager_config.static_config.timeouts.precommit_timeout": { + "consensus_manager_config.consensus_manager_config.dynamic_config.timeouts.precommit_timeout": { "description": "The timeout (seconds) for a precommit.", "privacy": "Public", "value": 1.0 }, - "consensus_manager_config.consensus_manager_config.static_config.timeouts.prevote_timeout": { + "consensus_manager_config.consensus_manager_config.dynamic_config.timeouts.prevote_timeout": { "description": "The timeout (seconds) for a prevote.", "privacy": "Public", "value": 1.0 }, - "consensus_manager_config.consensus_manager_config.static_config.timeouts.proposal_timeout": { + "consensus_manager_config.consensus_manager_config.dynamic_config.timeouts.proposal_timeout": { "description": "The timeout (seconds) for a proposal.", "privacy": "Public", "value": 3.0 @@ -1579,11 +1589,6 @@ "pointer_target": "chain_id", "privacy": "Public" }, - "consensus_manager_config.context_config.constant_l2_gas_price": { - "description": "If true, sets STRK gas price to its minimum price from the versioned constants.", - "privacy": "Public", - "value": false - }, "consensus_manager_config.context_config.l1_da_mode": { "description": "The data availability mode, true: Blob, false: Calldata.", "privacy": "Public", @@ -1624,6 +1629,46 @@ "privacy": "Public", "value": 1 }, + "consensus_manager_config.context_config.override_eth_to_fri_rate": { + "description": "Replace the Eth-to-Fri conversion rate with this value.", + "privacy": "Public", + "value": 0 + }, + "consensus_manager_config.context_config.override_eth_to_fri_rate.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, + "consensus_manager_config.context_config.override_l1_data_gas_price_wei": { + "description": "Replace the L1 data gas price (wei) with this value.", + "privacy": "Public", + "value": 0 + }, + "consensus_manager_config.context_config.override_l1_data_gas_price_wei.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, + "consensus_manager_config.context_config.override_l1_gas_price_wei": { + "description": "Replace the L1 gas price (wei) with this value.", + "privacy": "Public", + "value": 0 + }, + "consensus_manager_config.context_config.override_l1_gas_price_wei.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, + "consensus_manager_config.context_config.override_l2_gas_price_fri": { + "description": "Replace the L2 gas price (fri) with this value.", + "privacy": "Public", + "value": 0 + }, + "consensus_manager_config.context_config.override_l2_gas_price_fri.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, "consensus_manager_config.context_config.proposal_buffer_size": { "description": "The buffer size for streaming outbound proposals.", "privacy": "Public", @@ -1634,6 +1679,16 @@ "privacy": "Public", "value": 10000 }, + "consensus_manager_config.context_config.validator_ids": { + "description": "Optional explicit set of validator IDs (comma separated).", + "privacy": "Public", + "value": "" + }, + "consensus_manager_config.context_config.validator_ids.#is_none": { + "description": "Flag for an optional field.", + "privacy": "TemporaryValue", + "value": true + }, "consensus_manager_config.immediate_active_height": { "description": "The height at which the node may actively participate in consensus.", "privacy": "Public", @@ -1825,7 +1880,7 @@ "privacy": "Public" }, "gateway_config.stateful_tx_validator_config.versioned_constants_overrides.max_n_events": { - "description": "Maximum number of events that can be emitted from the transation.", + "description": "Maximum number of events that can be emitted from the transaction.", "pointer_target": "versioned_constants_overrides.max_n_events", "privacy": "Public" }, @@ -1854,6 +1909,11 @@ "privacy": "Public", "value": 4089446 }, + "gateway_config.stateless_tx_validator_config.max_l2_gas_amount": { + "description": "Maximum allowed L2 gas amount for transactions.", + "privacy": "Public", + "value": 1200000000 + }, "gateway_config.stateless_tx_validator_config.max_sierra_version.major": { "description": "The major version of the configuration.", "privacy": "Public", @@ -2289,6 +2349,11 @@ "privacy": "TemporaryValue", "value": false }, + "sierra_compiler_config.audited_libfuncs_only": { + "description": "If true, restrict to audited libfuncs. Otherwise allow all.", + "privacy": "Public", + "value": true + }, "sierra_compiler_config.max_bytecode_size": { "description": "Limitation of compiled CASM bytecode size (felts).", "privacy": "Public", @@ -2695,7 +2760,7 @@ "value": 10000000 }, "versioned_constants_overrides.max_n_events": { - "description": "Maximum number of events that can be emitted from the transation.", + "description": "Maximum number of events that can be emitted from the transaction.", "privacy": "TemporaryValue", "value": 1000 }, diff --git a/crates/apollo_node/src/bin/update_apollo_node_config_schema.rs b/crates/apollo_node/src/bin/update_apollo_node_config_schema.rs index 99d134ea16b..3a95ed88b1b 100644 --- a/crates/apollo_node/src/bin/update_apollo_node_config_schema.rs +++ b/crates/apollo_node/src/bin/update_apollo_node_config_schema.rs @@ -15,5 +15,5 @@ fn main() { .dump_to_file(&CONFIG_POINTERS, &CONFIG_NON_POINTERS_WHITELIST, CONFIG_SCHEMA_PATH) .expect("dump to file error"); - serialize_to_file(private_parameters(), CONFIG_SECRETS_SCHEMA_PATH); + serialize_to_file(&private_parameters(), CONFIG_SECRETS_SCHEMA_PATH); } diff --git a/crates/apollo_node/src/components.rs b/crates/apollo_node/src/components.rs index a6ff086224b..5e40a9187f3 100644 --- a/crates/apollo_node/src/components.rs +++ b/crates/apollo_node/src/components.rs @@ -125,39 +125,40 @@ pub async fn create_node_components( ReactiveComponentExecutionMode::Disabled | ReactiveComponentExecutionMode::Remote => None, }; - let (config_manager, config_manager_runner) = match config - .components - .config_manager - .execution_mode - { - ReactiveComponentExecutionMode::LocalExecutionWithRemoteDisabled => { - let config_manager_config = - config.config_manager_config.as_ref().expect("Config Manager config should be set"); - let config_manger = - ConfigManager::new(config_manager_config.clone(), NodeDynamicConfig::from(config)); - let config_manager_client = clients - .get_config_manager_shared_client() - .expect("Config Manager client should be available"); - let config_manager_runner = ConfigManagerRunner::new( - config_manager_config.clone(), - config_manager_client, - cli_args, - ); - (Some(config_manger), Some(config_manager_runner)) - } + let (config_manager, config_manager_runner) = + match config.components.config_manager.execution_mode { + ReactiveComponentExecutionMode::LocalExecutionWithRemoteDisabled => { + let node_dynamic_config = NodeDynamicConfig::from(config); + let config_manager_config = config + .config_manager_config + .as_ref() + .expect("Config Manager config should be set"); + let config_manger = + ConfigManager::new(config_manager_config.clone(), node_dynamic_config.clone()); + let config_manager_client = clients + .get_config_manager_shared_client() + .expect("Config Manager client should be available"); + let config_manager_runner = ConfigManagerRunner::new( + config_manager_config.clone(), + config_manager_client, + node_dynamic_config, + cli_args, + ); + (Some(config_manger), Some(config_manager_runner)) + } - ReactiveComponentExecutionMode::LocalExecutionWithRemoteEnabled - | ReactiveComponentExecutionMode::Remote => { - panic!( - "ConfigManager does not support remote mode - it's a local infrastructure \ - component" - ); - } - ReactiveComponentExecutionMode::Disabled => { - // TODO(tsabary): assert config is not set. - (None, None) - } - }; + ReactiveComponentExecutionMode::LocalExecutionWithRemoteEnabled + | ReactiveComponentExecutionMode::Remote => { + panic!( + "ConfigManager does not support remote mode - it's a local infrastructure \ + component" + ); + } + ReactiveComponentExecutionMode::Disabled => { + // TODO(tsabary): assert config is not set. + (None, None) + } + }; let consensus_manager = match config.components.consensus_manager.execution_mode { ActiveComponentExecutionMode::Enabled => { @@ -381,10 +382,13 @@ pub async fn create_node_components( clients.get_l1_endpoint_monitor_shared_client().unwrap(); let base_layer = EthereumBaseLayerContract::new(base_layer_config.clone(), initial_node_url.clone()); + // TODO(guyn): figure out how to start in case we can't reach L1 to get the start block. + // TODO(guyn): maybe put the fetch_start_block logic inside the scraper loop? let l1_start_block = fetch_start_block(&base_layer, l1_scraper_config) .await .unwrap_or_else(|err| panic!("Error while initializing the L1 scraper: {err}")); + debug!("L1 start block: {l1_start_block:?}"); let monitored_base_layer = MonitoredEthereumBaseLayer::new(base_layer, l1_endpoint_monitor_client).await; @@ -474,6 +478,8 @@ pub async fn create_node_components( base_layer_config.clone(), initial_node_url.clone(), ); + // Which L2 block is already proved at the L1 height the scraper was initialized on. + // It is safe to start syncing the provider from this height. let scraper_synced_startup_height = base_layer .get_proved_block_at(l1_scraper_start_l1_height) .await diff --git a/crates/apollo_node/src/test_utils/node_runner.rs b/crates/apollo_node/src/test_utils/node_runner.rs index 1455e963285..8da58ab9746 100644 --- a/crates/apollo_node/src/test_utils/node_runner.rs +++ b/crates/apollo_node/src/test_utils/node_runner.rs @@ -18,22 +18,20 @@ const TEMP_LOGS_DIR: &str = "integration_test_temporary_logs"; #[derive(Debug, Clone)] pub struct NodeRunner { node_index: usize, - executable_index: usize, } impl NodeRunner { - pub fn new(node_index: usize, executable_index: usize) -> Self { + pub fn new(node_index: usize) -> Self { create_dir_all(TEMP_LOGS_DIR).unwrap(); - Self { node_index, executable_index } + Self { node_index } } pub fn get_description(&self) -> String { - format!("Node id {} part {}:", self.node_index, self.executable_index) + format!("Node id {}:", self.node_index) } pub fn logs_file_path(&self) -> PathBuf { - PathBuf::from(TEMP_LOGS_DIR) - .join(format!("node_{}_part_{}.log", self.node_index, self.executable_index)) + PathBuf::from(TEMP_LOGS_DIR).join(format!("node_{}.log", self.node_index)) } } diff --git a/crates/apollo_node_config/src/component_config.rs b/crates/apollo_node_config/src/component_config.rs index 82c151930d6..4b403979439 100644 --- a/crates/apollo_node_config/src/component_config.rs +++ b/crates/apollo_node_config/src/component_config.rs @@ -122,8 +122,10 @@ impl ComponentConfig { } #[cfg(any(feature = "testing", test))] -pub fn set_urls_to_localhost(component_configs: &mut [ComponentConfig]) { - for component_config in component_configs.iter_mut() { - component_config.set_urls_to_localhost(); +pub fn set_urls_to_localhost<'a>( + component_configs: impl IntoIterator, +) { + for config in component_configs { + config.set_urls_to_localhost(); } } diff --git a/crates/apollo_node_config/src/component_execution_config.rs b/crates/apollo_node_config/src/component_execution_config.rs index 730a91e8ee1..1cae06885d1 100644 --- a/crates/apollo_node_config/src/component_execution_config.rs +++ b/crates/apollo_node_config/src/component_execution_config.rs @@ -13,7 +13,7 @@ use crate::config_utils::create_validation_error; use crate::definitions::ConfigExpectation::{self, Redundant, Required}; use crate::definitions::ConfigPresence::{self, Absent, Present}; -const DEFAULT_URL: &str = "localhost"; +pub const DEFAULT_URL: &str = "localhost"; const DEFAULT_IP: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED); const DEFAULT_INVALID_PORT: u16 = 0; @@ -189,6 +189,14 @@ impl ReactiveComponentExecutionConfig { self } + pub fn with_retries(mut self, retries: usize) -> Self { + self.remote_client_config + .as_mut() + .expect("Remote client config should be available") + .retries = retries; + self + } + #[cfg(any(feature = "testing", test))] pub fn set_url_to_localhost(&mut self) { self.url = Ipv4Addr::LOCALHOST.to_string(); diff --git a/crates/apollo_node_config/src/config_test.rs b/crates/apollo_node_config/src/config_test.rs index c2e04555dfa..18d8b4255b1 100644 --- a/crates/apollo_node_config/src/config_test.rs +++ b/crates/apollo_node_config/src/config_test.rs @@ -99,7 +99,7 @@ fn default_config_file_is_up_to_date() { .unwrap(); serialize_to_file_test(&combined_map, CONFIG_SCHEMA_PATH, FIX_BINARY_NAME); - serialize_to_file_test(private_parameters(), CONFIG_SECRETS_SCHEMA_PATH, FIX_BINARY_NAME); + serialize_to_file_test(&private_parameters(), CONFIG_SECRETS_SCHEMA_PATH, FIX_BINARY_NAME); } #[test] diff --git a/crates/apollo_node_config/src/config_utils.rs b/crates/apollo_node_config/src/config_utils.rs index 25611925f21..bb1a69c2d34 100644 --- a/crates/apollo_node_config/src/config_utils.rs +++ b/crates/apollo_node_config/src/config_utils.rs @@ -98,7 +98,8 @@ pub fn config_to_preset(config_map: &Value) -> Value { } } -/// Keep "{prefix}.#is_none": true, remove all other "{prefix}.*" keys. +/// Keep "{prefix}.#is_none": true, remove all other keys that begin with "{prefix}" (including +/// the bare prefix). pub fn prune_by_is_none(mut v: Value) -> Value { let obj: &mut Map = v.as_object_mut().expect("prune_by_is_none: expected a JSON object"); @@ -110,16 +111,17 @@ pub fn prune_by_is_none(mut v: Value) -> Value { for (k, val) in obj.iter() { if let Some(prefix) = k.strip_suffix(&is_none_suffix) { if val.as_bool() == Some(true) { - unset_optional_param_paths.insert(format!("{prefix}{FIELD_SEPARATOR}")); + unset_optional_param_paths.insert(prefix.to_string()); } } } - // Remove keys that begin with any such prefix, except the "#is_none" flag itself + // Remove keys that begin with any such prefix (including the bare prefix), except the + // "#is_none" flag itself obj.retain(|k, _| { if let Some(p) = unset_optional_param_paths.iter().find(|p| k.starts_with(&***p)) { // keep only the "{prefix}.#is_none" key - k == &format!("{p}{IS_NONE_MARK}") + k == &format!("{p}{FIELD_SEPARATOR}{IS_NONE_MARK}") } else { true } @@ -202,6 +204,7 @@ impl DeploymentBaseAppConfig { // Extract only the required fields from the config map. let preset = config_to_preset(&config_as_map); + let preset = prune_by_is_none(preset); validate_all_pointer_targets_set(preset.clone()).expect("Pointer target not set"); preset } @@ -210,7 +213,7 @@ impl DeploymentBaseAppConfig { pub fn dump_config_file(&self, config_path: &Path) { let value = self.as_value(); serialize_to_file( - value, + &value, config_path.to_str().expect("Should be able to convert path to string"), ); } diff --git a/crates/apollo_node_config/src/node_config.rs b/crates/apollo_node_config/src/node_config.rs index 2b6c114ba21..297a035290d 100644 --- a/crates/apollo_node_config/src/node_config.rs +++ b/crates/apollo_node_config/src/node_config.rs @@ -294,6 +294,16 @@ pub struct NodeDynamicConfig { pub mempool_dynamic_config: Option, } +impl SerializeConfig for NodeDynamicConfig { + fn dump(&self) -> BTreeMap { + let sub_configs = [ + ser_optional_sub_config(&self.consensus_dynamic_config, "consensus_dynamic_config"), + ser_optional_sub_config(&self.mempool_dynamic_config, "mempool_dynamic_config"), + ]; + sub_configs.into_iter().flatten().collect() + } +} + impl From<&SequencerNodeConfig> for NodeDynamicConfig { fn from(sequencer_node_config: &SequencerNodeConfig) -> Self { // TODO(Nadin/Tsabary): consider creating a macro for this. diff --git a/crates/apollo_proc_macros_tests/tests/latency_histogram.rs b/crates/apollo_proc_macros_tests/tests/latency_histogram.rs index 9408d86821c..37889f68007 100644 --- a/crates/apollo_proc_macros_tests/tests/latency_histogram.rs +++ b/crates/apollo_proc_macros_tests/tests/latency_histogram.rs @@ -1,6 +1,11 @@ use std::sync::OnceLock; -use apollo_metrics::metrics::{MetricHistogram, MetricScope, COLLECT_SEQUENCER_PROFILING_METRICS}; +use apollo_metrics::metrics::{ + MetricCommon, + MetricHistogram, + MetricScope, + COLLECT_SEQUENCER_PROFILING_METRICS, +}; use apollo_proc_macros::{latency_histogram, sequencer_latency_histogram}; use apollo_test_utils::prometheus_is_contained; use metrics::set_default_local_recorder; diff --git a/crates/apollo_protobuf/Cargo.toml b/crates/apollo_protobuf/Cargo.toml index 080450b4548..ab4a083af13 100644 --- a/crates/apollo_protobuf/Cargo.toml +++ b/crates/apollo_protobuf/Cargo.toml @@ -17,6 +17,7 @@ required-features = ["bin-deps"] [dependencies] apollo_test_utils = { workspace = true, optional = true } bytes.workspace = true +derive_more.workspace = true indexmap.workspace = true lazy_static.workspace = true papyrus_common.workspace = true diff --git a/crates/apollo_protobuf/src/consensus.rs b/crates/apollo_protobuf/src/consensus.rs index 6d638f1f7e6..aa2e43e7c6b 100644 --- a/crates/apollo_protobuf/src/consensus.rs +++ b/crates/apollo_protobuf/src/consensus.rs @@ -7,12 +7,29 @@ use std::fmt::Display; use bytes::{Buf, BufMut}; use prost::DecodeError; use serde::{Deserialize, Serialize}; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::consensus_transaction::ConsensusTransaction; use starknet_api::core::ContractAddress; use starknet_api::data_availability::L1DataAvailabilityMode; +use starknet_api::hash::StarkHash; use crate::converters::ProtobufConversionError; +#[derive( + Debug, + Default, + Copy, + Clone, + Eq, + PartialEq, + Hash, + Deserialize, + Serialize, + PartialOrd, + Ord, + derive_more::Display, + derive_more::Deref, +)] +pub struct ProposalCommitment(pub StarkHash); pub trait IntoFromProto: Into> + TryFrom, Error = ProtobufConversionError> {} impl IntoFromProto for T where @@ -32,7 +49,7 @@ pub struct Vote { pub vote_type: VoteType, pub height: u64, pub round: u32, - pub block_hash: Option, + pub proposal_commitment: Option, pub voter: ContractAddress, } @@ -101,12 +118,12 @@ pub struct TransactionBatch { pub transactions: Vec, } -/// The proposal is done when receiving this fin message, which contains the block hash. +/// The proposal is done when receiving this fin message, which contains the proposal commitment. #[derive(Debug, Clone, PartialEq)] pub struct ProposalFin { - /// The block hash of the proposed block. + /// The commitment identifying the proposed block. /// TODO(Matan): Consider changing the content ID to a signature. - pub proposal_commitment: BlockHash, + pub proposal_commitment: ProposalCommitment, } /// A part of the proposal. diff --git a/crates/apollo_protobuf/src/converters/class.rs b/crates/apollo_protobuf/src/converters/class.rs index e664554f08f..9b0998be8df 100644 --- a/crates/apollo_protobuf/src/converters/class.rs +++ b/crates/apollo_protobuf/src/converters/class.rs @@ -9,7 +9,7 @@ use papyrus_common::pending_classes::ApiContractClass; use papyrus_common::python_json::PythonJsonFormatter; use prost::Message; use serde::Serialize; -use starknet_api::compression_utils::{compress_and_encode, decode_and_decompress}; +use starknet_api::compression_utils::{compress_and_encode, decode_and_decompress_with_size_limit}; use starknet_api::contract_class::EntryPointType; use starknet_api::core::{ClassHash, EntryPointSelector}; use starknet_api::data_availability::DataAvailabilityMode; @@ -129,7 +129,10 @@ impl TryFrom for deprecated_contract_class::ContractClass ); } let abi = serde_json::from_str(&value.abi)?; - let program = decode_and_decompress(&value.program)?; + // TODO(dan): use config for this. + const MAX_CAIRO0_PROGRAM_SIZE: usize = 4 * 1024 * 1024; // 4MB + let program = + decode_and_decompress_with_size_limit(&value.program, MAX_CAIRO0_PROGRAM_SIZE)?; Ok(Self { program, entry_points_by_type, abi }) } diff --git a/crates/apollo_protobuf/src/converters/consensus.rs b/crates/apollo_protobuf/src/converters/consensus.rs index 7512eedb0ce..e9d6f4cca72 100644 --- a/crates/apollo_protobuf/src/converters/consensus.rs +++ b/crates/apollo_protobuf/src/converters/consensus.rs @@ -5,7 +5,7 @@ mod consensus_test; use std::convert::{TryFrom, TryInto}; use prost::Message; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::consensus_transaction::ConsensusTransaction; use starknet_api::hash::StarkHash; @@ -17,6 +17,7 @@ use super::common::{ use crate::consensus::{ ConsensusBlockInfo, IntoFromProto, + ProposalCommitment, ProposalFin, ProposalInit, ProposalPart, @@ -29,6 +30,21 @@ use crate::consensus::{ use crate::converters::ProtobufConversionError; use crate::{auto_impl_into_and_try_from_vec_u8, protobuf}; +impl TryFrom for ProposalCommitment { + type Error = ProtobufConversionError; + + fn try_from(value: protobuf::Hash) -> Result { + let stark_hash: StarkHash = value.try_into()?; + Ok(ProposalCommitment(stark_hash)) + } +} + +impl From for protobuf::Hash { + fn from(value: ProposalCommitment) -> Self { + value.0.into() + } +} + impl TryFrom for VoteType { type Error = ProtobufConversionError; @@ -57,11 +73,14 @@ impl TryFrom for Vote { let height = value.height; let round = value.round; - let block_hash: Option = - value.block_hash.map(|block_hash| block_hash.try_into()).transpose()?.map(BlockHash); + let proposal_commitment: Option = value + .proposal_commitment + .map(|proposal_commitment| proposal_commitment.try_into()) + .transpose()? + .map(ProposalCommitment); let voter = value.voter.ok_or(missing("voter"))?.try_into()?; - Ok(Vote { vote_type, height, round, block_hash, voter }) + Ok(Vote { vote_type, height, round, proposal_commitment, voter }) } } @@ -76,7 +95,7 @@ impl From for protobuf::Vote { vote_type: i32::from(vote_type), height: value.height, round: value.round, - block_hash: value.block_hash.map(|hash| hash.0.into()), + proposal_commitment: value.proposal_commitment.map(|commitment| commitment.0.into()), voter: Some(value.voter.into()), } } @@ -258,16 +277,15 @@ auto_impl_into_and_try_from_vec_u8!(TransactionBatch, protobuf::TransactionBatch impl TryFrom for ProposalFin { type Error = ProtobufConversionError; fn try_from(value: protobuf::ProposalFin) -> Result { - let proposal_commitment: StarkHash = + let proposal_commitment: ProposalCommitment = value.proposal_commitment.ok_or(missing("proposal_commitment"))?.try_into()?; - let proposal_commitment = BlockHash(proposal_commitment); Ok(ProposalFin { proposal_commitment }) } } impl From for protobuf::ProposalFin { fn from(value: ProposalFin) -> Self { - protobuf::ProposalFin { proposal_commitment: Some(value.proposal_commitment.0.into()) } + protobuf::ProposalFin { proposal_commitment: Some(value.proposal_commitment.into()) } } } diff --git a/crates/apollo_protobuf/src/converters/test_instances.rs b/crates/apollo_protobuf/src/converters/test_instances.rs index 15d2549ee11..80df71b5f4b 100644 --- a/crates/apollo_protobuf/src/converters/test_instances.rs +++ b/crates/apollo_protobuf/src/converters/test_instances.rs @@ -3,14 +3,16 @@ use std::fmt::Display; use apollo_test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use prost::DecodeError; use rand::Rng; -use starknet_api::block::{BlockHash, BlockNumber, GasPrice}; +use starknet_api::block::{BlockNumber, GasPrice}; use starknet_api::consensus_transaction::ConsensusTransaction; use starknet_api::core::ContractAddress; use starknet_api::data_availability::L1DataAvailabilityMode; +use starknet_api::hash::StarkHash; use super::ProtobufConversionError; use crate::consensus::{ ConsensusBlockInfo, + ProposalCommitment, ProposalFin, ProposalInit, ProposalPart, @@ -26,7 +28,7 @@ auto_impl_get_test_instance! { pub vote_type: VoteType, pub height: u64, pub round: u32, - pub block_hash: Option, + pub proposal_commitment: Option, pub voter: ContractAddress, } pub enum VoteType { @@ -39,8 +41,9 @@ auto_impl_get_test_instance! { pub valid_round: Option, pub proposer: ContractAddress, } + pub struct ProposalCommitment(pub StarkHash); pub struct ProposalFin { - pub proposal_commitment: BlockHash, + pub proposal_commitment: ProposalCommitment, } pub struct TransactionBatch { pub transactions: Vec, diff --git a/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto b/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto index 6e7a55b4e97..076ec548935 100644 --- a/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto +++ b/crates/apollo_protobuf/src/proto/p2p/proto/consensus/consensus.proto @@ -8,10 +8,10 @@ option go_package = "github.com/starknet-io/starknet-p2pspecs/p2p/proto/consensu // in a new block. message ConsensusTransaction { oneof txn { - DeclareV3WithClass declare_v3 = 1; + DeclareV3WithClass declare_v3 = 1; DeployAccountV3 deploy_account_v3 = 2; - InvokeV3 invoke_v3 = 3; - L1HandlerV0 l1_handler = 4; + InvokeV3 invoke_v3 = 3; + L1HandlerV0 l1_handler = 4; } Hash transaction_hash = 5; } @@ -25,39 +25,39 @@ message Vote { // We use a type field to distinguish between prevotes and precommits instead of different // messages, to make sure the data, and therefore the signatures, are unambiguous between // Prevote and Precommit. - VoteType vote_type = 2; - uint64 height = 3; - uint32 round = 4; + VoteType vote_type = 2; + uint64 height = 3; + uint32 round = 4; // This is optional since a vote can be NIL. - optional Hash block_hash = 5; - Address voter = 6; + optional Hash proposal_commitment = 5; + Address voter = 6; } message StreamMessage { oneof message { bytes content = 1; - Fin fin = 2; + Fin fin = 2; } - bytes stream_id = 3; + bytes stream_id = 3; uint64 message_id = 4; } message ProposalInit { - uint64 height = 1; - uint32 round = 2; + uint64 height = 1; + uint32 round = 2; optional uint32 valid_round = 3; - Address proposer = 4; + Address proposer = 4; } message BlockInfo { - uint64 height = 1; - uint64 timestamp = 2; - Address builder = 3; + uint64 height = 1; + uint64 timestamp = 2; + Address builder = 3; L1DataAvailabilityMode l1_da_mode = 4; - Uint128 l2_gas_price_fri = 5; - Uint128 l1_gas_price_wei = 6; - Uint128 l1_data_gas_price_wei = 7; - Uint128 eth_to_fri_rate = 8; + Uint128 l2_gas_price_fri = 5; + Uint128 l1_gas_price_wei = 6; + Uint128 l1_data_gas_price_wei = 7; + Uint128 eth_to_fri_rate = 8; } message TransactionBatch { @@ -81,10 +81,10 @@ message ProposalFin { // 5. executed_transaction_count is sent once message ProposalPart { oneof message { - ProposalInit init = 1; - ProposalFin fin = 2; - BlockInfo block_info = 3; - TransactionBatch transactions = 4; + ProposalInit init = 1; + ProposalFin fin = 2; + BlockInfo block_info = 3; + TransactionBatch transactions = 4; uint64 executed_transaction_count = 5; } } diff --git a/crates/apollo_protobuf/src/protobuf/protoc_output.rs b/crates/apollo_protobuf/src/protobuf/protoc_output.rs index 1694a0a9680..9351a5104ff 100644 --- a/crates/apollo_protobuf/src/protobuf/protoc_output.rs +++ b/crates/apollo_protobuf/src/protobuf/protoc_output.rs @@ -368,7 +368,7 @@ pub struct Vote { pub round: u32, /// This is optional since a vote can be NIL. #[prost(message, optional, tag = "5")] - pub block_hash: ::core::option::Option, + pub proposal_commitment: ::core::option::Option, #[prost(message, optional, tag = "6")] pub voter: ::core::option::Option
, } diff --git a/crates/apollo_reverts/Cargo.toml b/crates/apollo_reverts/Cargo.toml index e8aa635356b..5af23f955e7 100644 --- a/crates/apollo_reverts/Cargo.toml +++ b/crates/apollo_reverts/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] apollo_config.workspace = true +apollo_metrics.workspace = true apollo_storage.workspace = true futures.workspace = true serde.workspace = true diff --git a/crates/apollo_reverts/src/lib.rs b/crates/apollo_reverts/src/lib.rs index 533353eebbd..1767ef2383d 100644 --- a/crates/apollo_reverts/src/lib.rs +++ b/crates/apollo_reverts/src/lib.rs @@ -3,6 +3,7 @@ use std::future::Future; use apollo_config::dumping::{ser_param, SerializeConfig}; use apollo_config::{ParamPath, ParamPrivacyInput, SerializedParam}; +use apollo_metrics::metrics::MetricGauge; use apollo_storage::base_layer::BaseLayerStorageWriter; use apollo_storage::body::BodyStorageWriter; use apollo_storage::class_manager::ClassManagerStorageWriter; @@ -54,11 +55,16 @@ impl SerializeConfig for RevertConfig { } } +pub struct RevertComponentData { + pub name: &'static str, + pub revert_metric: MetricGauge, +} + pub async fn revert_blocks_and_eternal_pending( mut storage_height_marker: BlockNumber, revert_up_to_and_including: BlockNumber, mut revert_block_fn: impl FnMut(BlockNumber) -> Fut, - component_name: &str, + component: &RevertComponentData, ) -> Never where Fut: Future, @@ -66,6 +72,7 @@ where // If we revert all blocks up to height X (including), the new height marker will be X. let target_height_marker = revert_up_to_and_including; + let RevertComponentData { name: component_name, revert_metric } = component; if storage_height_marker <= target_height_marker { info!( "{component_name}'s storage height marker {storage_height_marker} is not larger than \ @@ -84,6 +91,7 @@ where ); info!("Reverting {component_name}'s storage to height marker {storage_height_marker}."); revert_block_fn(storage_height_marker).await; + revert_metric.set_lossy(storage_height_marker.0); info!( "Successfully reverted {component_name}'s storage to height marker \ {storage_height_marker}." diff --git a/crates/apollo_rpc/src/lib.rs b/crates/apollo_rpc/src/lib.rs index d20e6700efc..b44e9cbaaf5 100644 --- a/crates/apollo_rpc/src/lib.rs +++ b/crates/apollo_rpc/src/lib.rs @@ -67,6 +67,8 @@ const GENESIS_HASH: &str = "0x0"; /// Maximum size of a supported transaction body - 10MB. pub const SERVER_MAX_BODY_SIZE: u32 = 10 * 1024 * 1024; +pub const RPC_CONFIG_DEFAULT_PORT: u16 = 8090; + #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Validate)] pub struct RpcConfig { #[validate(custom = "validate_ascii")] @@ -87,7 +89,7 @@ impl Default for RpcConfig { RpcConfig { chain_id: ChainId::Mainnet, ip: "0.0.0.0".parse().unwrap(), - port: 8090, + port: RPC_CONFIG_DEFAULT_PORT, max_events_chunk_size: 1000, max_events_keys: 100, collect_metrics: false, diff --git a/crates/apollo_rpc/src/v0_8/api/mod.rs b/crates/apollo_rpc/src/v0_8/api/mod.rs index ccd892235cf..5d9a1d5ae23 100644 --- a/crates/apollo_rpc/src/v0_8/api/mod.rs +++ b/crates/apollo_rpc/src/v0_8/api/mod.rs @@ -673,6 +673,7 @@ pub(crate) fn decompress_program( base64::decode(base64_compressed_program).map_err(internal_server_error)?; let compressed_data = base64::decode(base64_compressed_program).map_err(internal_server_error)?; + // TODO(dan): add time and size limits. let mut decoder = GzDecoder::new(compressed_data.as_slice()); let mut decompressed = Vec::new(); decoder.read_to_end(&mut decompressed).map_err(internal_server_error)?; diff --git a/crates/apollo_rpc_execution/src/state_reader.rs b/crates/apollo_rpc_execution/src/state_reader.rs index 71f5180630e..c04ab12e86f 100644 --- a/crates/apollo_rpc_execution/src/state_reader.rs +++ b/crates/apollo_rpc_execution/src/state_reader.rs @@ -213,14 +213,20 @@ impl BlockifierStateReader for ExecutionStateReader { class_hash: ClassHash, _compiled_class: &RunnableCompiledClass, ) -> StateResult { - // Try to read the compiled class hash v2 from the dedicated stateless table. - // If it's missing (e.g., class not declared/Cairo0), return the default value. - let maybe_hash = self - .storage_reader - .begin_ro_txn() - .map_err(storage_err_to_state_err)? - .get_executable_class_hash_v2(&class_hash) - .map_err(storage_err_to_state_err)?; + let maybe_hash = + if let Some((class_manager_client, run_time_handle)) = &self.class_manager_handle { + // First, try getting from class manager if available. + run_time_handle + .block_on(class_manager_client.get_executable_class_hash_v2(class_hash)) + .map_err(|e| StateError::StateReadError(e.to_string()))? + } else { + // Fall back to reading from storage. + self.storage_reader + .begin_ro_txn() + .map_err(storage_err_to_state_err)? + .get_executable_class_hash_v2(&class_hash) + .map_err(storage_err_to_state_err)? + }; maybe_hash.ok_or(StateError::MissingCompiledClassHashV2(class_hash)) } diff --git a/crates/apollo_rpc_execution/src/test_utils.rs b/crates/apollo_rpc_execution/src/test_utils.rs index 3c35ca0b47c..14f0cedf6de 100644 --- a/crates/apollo_rpc_execution/src/test_utils.rs +++ b/crates/apollo_rpc_execution/src/test_utils.rs @@ -21,6 +21,7 @@ use starknet_api::block::{ GasPrice, GasPricePerToken, }; +use starknet_api::contract_class::compiled_class_hash::{HashVersion, HashableCompiledClass}; use starknet_api::contract_class::SierraVersion; use starknet_api::core::{ChainId, ClassHash, ContractAddress, Nonce, SequencerContractAddress}; use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; @@ -294,12 +295,14 @@ impl TxsScenarioBuilder { } pub fn declare_class(mut self, sender_address: ContractAddress) -> TxsScenarioBuilder { + let casm = get_test_casm(); let tx = ExecutableTransactionInput::DeclareV2( DeclareTransactionV2 { max_fee: *MAX_FEE, sender_address, nonce: self.next_nonce(sender_address), class_hash: self.next_class_hash(), + compiled_class_hash: casm.hash(&HashVersion::V2), ..Default::default() }, get_test_casm(), diff --git a/crates/apollo_sierra_compilation_config/src/config.rs b/crates/apollo_sierra_compilation_config/src/config.rs index b70e8516811..da1025f5012 100644 --- a/crates/apollo_sierra_compilation_config/src/config.rs +++ b/crates/apollo_sierra_compilation_config/src/config.rs @@ -8,6 +8,7 @@ use validator::Validate; // TODO(Noa): Reconsider the default values. pub const DEFAULT_MAX_BYTECODE_SIZE: usize = 80 * 1024; pub const DEFAULT_MAX_MEMORY_USAGE: u64 = 5 * 1024 * 1024 * 1024; +pub const DEFAULT_AUDITED_LIBFUNCS_ONLY: bool = true; #[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)] pub struct SierraCompilationConfig { @@ -15,6 +16,8 @@ pub struct SierraCompilationConfig { pub max_bytecode_size: usize, /// Compilation process’s virtual memory (address space) byte limit. pub max_memory_usage: Option, + /// If true, compile with audited libfuncs only; if false, allow all libfuncs. + pub audited_libfuncs_only: bool, } impl Default for SierraCompilationConfig { @@ -22,18 +25,27 @@ impl Default for SierraCompilationConfig { Self { max_bytecode_size: DEFAULT_MAX_BYTECODE_SIZE, max_memory_usage: Some(DEFAULT_MAX_MEMORY_USAGE), + audited_libfuncs_only: DEFAULT_AUDITED_LIBFUNCS_ONLY, } } } impl SerializeConfig for SierraCompilationConfig { fn dump(&self) -> BTreeMap { - let mut dump = BTreeMap::from([ser_param( - "max_bytecode_size", - &self.max_bytecode_size, - "Limitation of compiled CASM bytecode size (felts).", - ParamPrivacyInput::Public, - )]); + let mut dump = BTreeMap::from([ + ser_param( + "max_bytecode_size", + &self.max_bytecode_size, + "Limitation of compiled CASM bytecode size (felts).", + ParamPrivacyInput::Public, + ), + ser_param( + "audited_libfuncs_only", + &self.audited_libfuncs_only, + "If true, restrict to audited libfuncs. Otherwise allow all.", + ParamPrivacyInput::Public, + ), + ]); dump.extend(ser_optional_param( &self.max_memory_usage, DEFAULT_MAX_MEMORY_USAGE, diff --git a/crates/apollo_starknet_client/resources/reader/declare_v3.json b/crates/apollo_starknet_client/resources/reader/declare_v3.json index 55d422c8f31..7925feb4fa8 100644 --- a/crates/apollo_starknet_client/resources/reader/declare_v3.json +++ b/crates/apollo_starknet_client/resources/reader/declare_v3.json @@ -1,7 +1,7 @@ { "account_deployment_data": [], "class_hash": "0x6f6742127e27687640c7aaf566b9dc892c4493973076cb96fdeb418ae77c192", - "compiled_class_hash": "0xcbbcfda1dea5cc48d3c7ebefd50ecc4aaefabe7e5596fdef4f60cded0ea0b24", + "compiled_class_hash": "0x4bbcfda1dea5cb38d3c7ebefd50ecc4aaefabe7e5596fdef4f60cded0ea0b23", "fee_data_availability_mode": 0, "nonce": "0x0", "nonce_data_availability_mode": 0, diff --git a/crates/apollo_starknet_client/src/lib.rs b/crates/apollo_starknet_client/src/lib.rs index c2e67313075..d67dca5ebc4 100644 --- a/crates/apollo_starknet_client/src/lib.rs +++ b/crates/apollo_starknet_client/src/lib.rs @@ -103,7 +103,7 @@ impl StarknetClient { format!("{}; {}; {}", info.os_type(), info.version(), info.bitness()); let app_user_agent = format!( "{product_name}/{product_version} ({system_information})", - product_name = "papyrus", + product_name = "apollo", product_version = node_version, system_information = system_information ); diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/encrypt.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/encrypt.cairo index b46fe645e35..f0d5dfbfa9b 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/encrypt.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/encrypt.cairo @@ -21,19 +21,19 @@ from starkware.cairo.common.alloc import alloc // Part 1: Generate StarkNet keys for each committee member // - An hint generates a symmetric_key and sn_private_keys by hashing the compressed state diff. // - The private keys are validated to be in range [1, StarkCurve.ORDER - 1]. -// - Public keys are computed from the private keys and output to encrypted_dst. +// - Public keys are computed from the private keys and output to output_pointer. // // Part 2: Share one symmetric_key with multiple committee members // - For each committee member, derive a shared secret from their public key // and the corresponding StarkNet private key. // (Shared secret generation uses Elliptic-Curve Diffie–Hellman (ECDH) on StarkCurve). -// - Hash the shared secret's x-coordinate using BLAKE2s to get a mask, then output to encrypted_dst +// - Hash the shared secret's x-coordinate using BLAKE2s to get a mask, then output to output_pointer // encrypted_symmetric_key[i] = symmetric_key + mask[i]. // (A committee member can recompute the same mask with their private key to recover symmetric_key.) // // Part 3: Encrypt a list of felts with the symmetric_key // - For index i, compute mask_i = BLAKE2s(encode([symmetric_key, i])). -// - Output to encrypted_dst ciphertext[i] = plaintext[i] + mask_i (modulo the field prime). +// - Output to output_pointer ciphertext[i] = plaintext[i] + mask_i (modulo the field prime). // // Output structure: // encrypted = [n_keys, sn_public_keys, encrypted_symmetric_keys, ciphertext] @@ -44,14 +44,14 @@ from starkware.cairo.common.alloc import alloc // - Field addition is used for masking; this provides confidentiality only. // - Reference: Elliptic-Curve Diffie–Hellman (ECDH) https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman func encrypt_state_diff{range_check_ptr, ec_op_ptr: EcOpBuiltin*}( - compressed_start: felt*, compressed_dst: felt*, n_keys: felt, public_keys: felt* -) -> (encrypted_start: felt*, encrypted_dst: felt*) { + compressed_start: felt*, compressed_end: felt*, n_keys: felt, public_keys: felt* +) -> (encrypted_start: felt*, encrypted_end: felt*) { alloc_locals; // Generate random symmetric key and random starknet private keys. local symmetric_key: felt; local sn_private_keys: felt*; - %{ generate_keys_from_hash(ids.compressed_start, ids.compressed_dst, ids.n_keys) %} + %{ generate_keys_from_hash(ids.compressed_start, ids.compressed_end, ids.n_keys) %} local encrypted_start: felt*; %{ @@ -62,11 +62,11 @@ func encrypt_state_diff{range_check_ptr, ec_op_ptr: EcOpBuiltin*}( ids.encrypted_start = segments.add_temp_segment() %} - let encrypted_dst = encrypted_start; - assert encrypted_dst[0] = n_keys; - let encrypted_dst = &encrypted_dst[1]; + let output_pointer = encrypted_start; + assert output_pointer[0] = n_keys; + let output_pointer = &output_pointer[1]; - with encrypted_dst { + with output_pointer { output_sn_public_keys(n_keys=n_keys, sn_private_keys=sn_private_keys); output_encrypted_symmetric_key( n_keys=n_keys, @@ -74,17 +74,17 @@ func encrypt_state_diff{range_check_ptr, ec_op_ptr: EcOpBuiltin*}( sn_private_keys=sn_private_keys, symmetric_key=symmetric_key, ); - encrypt(data_start=compressed_start, data_end=compressed_dst, symmetric_key=symmetric_key); + encrypt(data_start=compressed_start, data_end=compressed_end, symmetric_key=symmetric_key); } - return (encrypted_start=encrypted_start, encrypted_dst=encrypted_dst); + return (encrypted_start=encrypted_start, encrypted_end=output_pointer); } // Compute public keys from private keys. // Step-by-step for each key: // 1) Multiply the private key by the curve generator to get the public point (x, y). -// 2) Write x into `encrypted_dst` (y can be recovered later when needed). -func output_sn_public_keys{range_check_ptr, ec_op_ptr: EcOpBuiltin*, encrypted_dst: felt*}( +// 2) Write x into `output` (y can be recovered later when needed). +func output_sn_public_keys{range_check_ptr, ec_op_ptr: EcOpBuiltin*, output_pointer: felt*}( n_keys: felt, sn_private_keys: felt* ) { if (n_keys == 0) { @@ -94,8 +94,8 @@ func output_sn_public_keys{range_check_ptr, ec_op_ptr: EcOpBuiltin*, encrypted_d let (sn_public_key) = ec_mul( m=sn_private_keys[0], p=EcPoint(x=StarkCurve.GEN_X, y=StarkCurve.GEN_Y) ); - assert encrypted_dst[0] = sn_public_key.x; - let encrypted_dst = &encrypted_dst[1]; + assert output_pointer[0] = sn_public_key.x; + let output_pointer = &output_pointer[1]; // Validates that the private keys are within the range [1, StarkCurve.ORDER - 1]. assert_not_zero(sn_private_keys[0]); assert_le_felt(sn_private_keys[0], StarkCurve.ORDER - 1); @@ -108,23 +108,25 @@ func output_sn_public_keys{range_check_ptr, ec_op_ptr: EcOpBuiltin*, encrypted_d // 2) Compute a shared secret point = our private key * recipient public point (Diffie–Hellman). // 3) Hash the x-coordinate of the shared point to get a mask. // 4) encrypted symmetric key = symmetric_key + hash(shared_secret.x)`. -func output_encrypted_symmetric_key{range_check_ptr, ec_op_ptr: EcOpBuiltin*, encrypted_dst: felt*}( - n_keys: felt, public_keys: felt*, sn_private_keys: felt*, symmetric_key: felt -) { +func output_encrypted_symmetric_key{ + range_check_ptr, ec_op_ptr: EcOpBuiltin*, output_pointer: felt* +}(n_keys: felt, public_keys: felt*, sn_private_keys: felt*, symmetric_key: felt) { if (n_keys == 0) { return (); } alloc_locals; + // Using recover_y(x) is safe because on short-Weierstrass curves (x, y) and (x, -y) share the same x. + // Scalar multiplication depends only on x(Q), so we can reconstruct y later without ambiguity. let (public_key) = recover_y(public_keys[0]); let (__fp__, _) = get_fp_and_pc(); let (local shared_secret) = ec_mul(m=sn_private_keys[0], p=public_key); let (hash) = calc_blake_hash_single(item=shared_secret.x); - assert encrypted_dst[0] = symmetric_key + hash; - let encrypted_dst = &encrypted_dst[1]; + assert output_pointer[0] = symmetric_key + hash; + let output_pointer = &output_pointer[1]; return output_encrypted_symmetric_key( n_keys=n_keys - 1, @@ -134,11 +136,11 @@ func output_encrypted_symmetric_key{range_check_ptr, ec_op_ptr: EcOpBuiltin*, en ); } -// Encrypt a list of numbers (felts) using symmetric_key into encrypted_dst. +// Encrypt a list of numbers (felts) using symmetric_key into output_pointer. // Step-by-step for item i: // 1) mask = Hash [encoded_symmetric_key, i] . // 2) ciphertext[i] = plaintext[i] + mask. -func encrypt{range_check_ptr, encrypted_dst: felt*}( +func encrypt{range_check_ptr, output_pointer: felt*}( data_start: felt*, data_end: felt*, symmetric_key: felt ) { // For all elements of the state diff, write the input and output to the same output to @@ -164,7 +166,7 @@ func encrypt{range_check_ptr, encrypted_dst: felt*}( // Helper for `encrypt` that processes one element at a time. // Stops when we reach `data_end`. -func encrypt_inner{range_check_ptr, encrypted_dst: felt*}( +func encrypt_inner{range_check_ptr, output_pointer: felt*}( data_start: felt*, data_end: felt*, index: felt, @@ -205,9 +207,9 @@ func encrypt_inner{range_check_ptr, encrypted_dst: felt*}( let blake_segment = &blake_segment[8]; // Encrypt the current element. - assert encrypted_dst[0] = hash + data_start[0]; + assert output_pointer[0] = hash + data_start[0]; - let encrypted_dst = &encrypted_dst[1]; + let output_pointer = &output_pointer[1]; return encrypt_inner( data_start=&data_start[1], diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/deprecated_execute_syscalls.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/deprecated_execute_syscalls.cairo index 70c0daadda7..7916033c07f 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/deprecated_execute_syscalls.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/execution/deprecated_execute_syscalls.cairo @@ -9,6 +9,8 @@ from starkware.cairo.common.memcpy import memcpy from starkware.cairo.common.segments import relocate_segment from starkware.starknet.common.constants import ORIGIN_ADDRESS from starkware.starknet.common.new_syscalls import ExecutionInfo +from starkware.cairo.common.math import assert_not_equal +from starkware.starknet.core.os.constants import EXECUTE_ENTRY_POINT_SELECTOR from starkware.starknet.common.syscalls import ( CALL_CONTRACT_SELECTOR, DELEGATE_CALL_SELECTOR, @@ -530,6 +532,8 @@ func execute_deprecated_syscalls{ // entries before this point belong to the caller. assert [revert_log] = RevertLogEntry(selector=CHANGE_CONTRACT_ENTRY, value=caller_address); let revert_log = &revert_log[1]; + // It is forbidded to call the `__execute__` function. + assert_not_equal(call_contract_syscall.request.selector, EXECUTE_ENTRY_POINT_SELECTOR); execute_contract_call_syscall( block_context=block_context, contract_address=callee_address, diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/naive_blake.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/naive_blake.cairo index d78e95868c4..42108a29258 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/naive_blake.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/naive_blake.cairo @@ -1,8 +1,8 @@ from starkware.cairo.common.alloc import alloc from starkware.cairo.common.cairo_blake2s.blake2s import blake_with_opcode -// Gets a felt that represent a 256-bit unsigned integer stored as an array of eight 32-bit unsigned integers -// represented in little-endian notation. Return the felt representation of the integer modulo prime. +// Gets a felt that represents a 256-bit unsigned integer stored as an array of eight 32-bit unsigned integers +// represented in little-endian notation. Returns the felt representation of the integer modulo prime. func felt_from_le_u32s(u32s: felt*) -> felt { let value = u32s[7] * 2 ** 224 + u32s[6] * 2 ** 192 + u32s[5] * 2 ** 160 + u32s[4] * 2 ** 128 + u32s[3] * 2 ** 96 + u32s[2] * 2 ** 64 + u32s[1] * 2 ** 32 + u32s[0]; @@ -61,11 +61,9 @@ func create_initial_state_for_blake2s() -> (initial_state: felt*) { // Encodes one felt252 into eight u32s represented in little-endian order. func naive_encode_felt252_to_u32s(packed_value: felt, unpacked_u32s: felt*) { %{ NaiveUnpackFelt252ToU32s %} - tempvar out = unpacked_u32s; - // TODO(Noa): Assert that the limbs represent a number in the range [0, PRIME-1]. // Assert that the limbs represent the number. - let actual_value = felt_from_le_u32s(u32s=out); + let actual_value = felt_from_le_u32s(u32s=unpacked_u32s); assert packed_value = actual_value; return (); diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo index eb8373a7e28..c04f4aeb5ed 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo @@ -490,7 +490,7 @@ func migrate_classes_to_v2_casm_hash{ let (casm_hash_v1) = poseidon_compiled_class_hash(compiled_class, full_contract=TRUE); let (casm_hash_v2) = blake_compiled_class_hash(compiled_class, full_contract=TRUE); %{ vm_exit_scope() %} - // Verify the guessed v2 hash. + // Sanity check: verify the guessed v2 hash. assert compiled_class_fact.hash = casm_hash_v2; // Update the casm hash from v1 to v2. dict_update{dict_ptr=contract_class_changes}( diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo index 6c19090245c..23ef0189931 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/output.cairo @@ -265,14 +265,14 @@ func process_data_availability{range_check_ptr, ec_op_ptr: EcOpBuiltin*}( } // Encrypt the compressed state updates. - let (encrypted_start, encrypted_dst) = encrypt_state_diff( + let (encrypted_start, encrypted_end) = encrypt_state_diff( compressed_start=compressed_start, - compressed_dst=compressed_dst, + compressed_end=compressed_dst, n_keys=n_keys, public_keys=public_keys, ); - return (da_start=encrypted_start, da_end=encrypted_dst); + return (da_start=encrypted_start, da_end=encrypted_end); } func serialize_data_availability{output_ptr: felt*}(da_start: felt*, da_end: felt*) { diff --git a/crates/apollo_starknet_os_program/src/program_hash.json b/crates/apollo_starknet_os_program/src/program_hash.json index 9190428b819..25c49182363 100644 --- a/crates/apollo_starknet_os_program/src/program_hash.json +++ b/crates/apollo_starknet_os_program/src/program_hash.json @@ -1,5 +1,5 @@ { - "os": "0xe4be3f413aec9b9f867ef2f5bf29e92d4c2849487c96317a4fb0e3eb38db13", + "os": "0x1c8437ab86fa4d9154bacb3fdbf3e5d7a63dba228748dd7d71f1d4986c2dc18", "aggregator": "0x78b933fe424c0c000b1da7f414cfb6362af87b694b9dd7a6a42aa500f3a6891", "aggregator_with_prefix": "0x3c2bf03c21bd2794125c481d7e224fbd8793ac675d24cdc9d476761d0d4ccab" } \ No newline at end of file diff --git a/crates/apollo_state_reader/src/papyrus_state.rs b/crates/apollo_state_reader/src/apollo_state.rs similarity index 97% rename from crates/apollo_state_reader/src/papyrus_state.rs rename to crates/apollo_state_reader/src/apollo_state.rs index 2145b73f8a8..73336578a18 100644 --- a/crates/apollo_state_reader/src/papyrus_state.rs +++ b/crates/apollo_state_reader/src/apollo_state.rs @@ -24,10 +24,10 @@ use starknet_api::state::{SierraContractClass, StateNumber, StorageKey}; use starknet_types_core::felt::Felt; #[cfg(test)] -#[path = "papyrus_state_test.rs"] +#[path = "apollo_state_test.rs"] mod test; -type RawPapyrusReader<'env> = apollo_storage::StorageTxn<'env, RO>; +type RawApolloReader<'env> = apollo_storage::StorageTxn<'env, RO>; pub struct ClassReader { pub reader: SharedClassManagerClient, @@ -88,14 +88,14 @@ impl ClassReader { } } -pub struct PapyrusReader { +pub struct ApolloReader { storage_reader: StorageReader, latest_block: BlockNumber, // Reader is `None` for reader invoked through `native_blockifier`. class_reader: Option, } -impl PapyrusReader { +impl ApolloReader { pub fn new_with_class_reader( storage_reader: StorageReader, latest_block: BlockNumber, @@ -108,7 +108,7 @@ impl PapyrusReader { Self { storage_reader, latest_block, class_reader: None } } - fn reader(&self) -> StateResult> { + fn reader(&self) -> StateResult> { self.storage_reader .begin_ro_txn() .map_err(|error| StateError::StateReadError(error.to_string())) @@ -195,7 +195,7 @@ impl PapyrusReader { } // Currently unused - will soon replace the same `impl` for `PapyrusStateReader`. -impl StateReader for PapyrusReader { +impl StateReader for ApolloReader { fn get_storage_at( &self, contract_address: ContractAddress, @@ -261,7 +261,7 @@ impl StateReader for PapyrusReader { } } -impl FetchCompiledClasses for PapyrusReader { +impl FetchCompiledClasses for ApolloReader { fn get_compiled_classes(&self, class_hash: ClassHash) -> StateResult { self.get_compiled_class_from_db(class_hash) } diff --git a/crates/apollo_state_reader/src/papyrus_state_test.rs b/crates/apollo_state_reader/src/apollo_state_test.rs similarity index 94% rename from crates/apollo_state_reader/src/papyrus_state_test.rs rename to crates/apollo_state_reader/src/apollo_state_test.rs index ce91f2c28de..d8d7c0db766 100644 --- a/crates/apollo_state_reader/src/papyrus_state_test.rs +++ b/crates/apollo_state_reader/src/apollo_state_test.rs @@ -19,7 +19,7 @@ use starknet_api::contract_class::ContractClass; use starknet_api::state::{StateDiff, StorageKey}; use starknet_api::{calldata, felt}; -use crate::papyrus_state::PapyrusReader; +use crate::apollo_state::ApolloReader; #[test] fn test_entry_point_with_papyrus_state() -> apollo_storage::StorageResult<()> { @@ -49,8 +49,8 @@ fn test_entry_point_with_papyrus_state() -> apollo_storage::StorageResult<()> { // BlockNumber is 1 due to the initialization step above. let block_number = BlockNumber(1); - let papyrus_reader = PapyrusReader::new(storage_reader, block_number); - let mut state = CachedState::from(papyrus_reader); + let apollo_reader = ApolloReader::new(storage_reader, block_number); + let mut state = CachedState::from(apollo_reader); // Call entrypoint that want to write to storage, which updates the cached state's write cache. let key = felt!(1234_u16); diff --git a/crates/apollo_state_reader/src/lib.rs b/crates/apollo_state_reader/src/lib.rs index 378444d69a2..2f4fcab7c63 100644 --- a/crates/apollo_state_reader/src/lib.rs +++ b/crates/apollo_state_reader/src/lib.rs @@ -1 +1 @@ -pub mod papyrus_state; +pub mod apollo_state; diff --git a/crates/apollo_state_sync/Cargo.toml b/crates/apollo_state_sync/Cargo.toml index 3bd806359f5..d1699c33179 100644 --- a/crates/apollo_state_sync/Cargo.toml +++ b/crates/apollo_state_sync/Cargo.toml @@ -34,8 +34,10 @@ tokio.workspace = true tracing.workspace = true [dev-dependencies] +apollo_starknet_client = { workspace = true, features = ["testing"] } apollo_storage = { workspace = true, features = ["testing"] } apollo_test_utils.workspace = true indexmap = { workspace = true, features = ["serde"] } libp2p.workspace = true +mockall.workspace = true rand_chacha.workspace = true diff --git a/crates/apollo_state_sync/src/lib.rs b/crates/apollo_state_sync/src/lib.rs index 9176f498fbc..dc33433ab3a 100644 --- a/crates/apollo_state_sync/src/lib.rs +++ b/crates/apollo_state_sync/src/lib.rs @@ -119,54 +119,58 @@ impl ComponentRequestHandler for StateSync impl StateSync { async fn get_block(&self, block_number: BlockNumber) -> StateSyncResult { let storage_reader = self.storage_reader.clone(); - tokio::task::spawn_blocking(move || { - let txn = storage_reader.begin_ro_txn()?; - let block_header = txn - .get_block_header(block_number)? - .ok_or(StateSyncError::BlockNotFound(block_number))?; - let block_transactions_with_hash = txn - .get_block_transactions_with_hash(block_number)? - .ok_or(StateSyncError::BlockNotFound(block_number))?; - let thin_state_diff = txn - .get_state_diff(block_number)? - .ok_or(StateSyncError::BlockNotFound(block_number))?; - drop(txn); // Drop txn so we don't unnecessarily hold it open during the procedure below. - - let mut l1_transaction_hashes: Vec = vec![]; - let mut account_transaction_hashes: Vec = vec![]; - - for (tx, tx_hash) in block_transactions_with_hash { - match tx { - Transaction::L1Handler(_) => l1_transaction_hashes.push(tx_hash), - _ => account_transaction_hashes.push(tx_hash), - } + let txn = storage_reader.begin_ro_txn()?; + + let block_header = txn + .get_block_header(block_number)? + .ok_or(StateSyncError::BlockNotFound(block_number))?; + let block_transactions_with_hash = txn + .get_block_transactions_with_hash(block_number)? + .ok_or(StateSyncError::BlockNotFound(block_number))?; + let thin_state_diff = + txn.get_state_diff(block_number)?.ok_or(StateSyncError::BlockNotFound(block_number))?; + drop(txn); // Drop txn so we don't unnecessarily hold it open during the procedure below. + + let mut l1_transaction_hashes: Vec = vec![]; + let mut account_transaction_hashes: Vec = vec![]; + + for (tx, tx_hash) in block_transactions_with_hash { + match tx { + Transaction::L1Handler(_) => l1_transaction_hashes.push(tx_hash), + _ => account_transaction_hashes.push(tx_hash), } + } - Ok(SyncBlock { - state_diff: thin_state_diff, - block_header_without_hash: block_header.block_header_without_hash, - account_transaction_hashes, - l1_transaction_hashes, - }) + Ok(SyncBlock { + state_diff: thin_state_diff, + block_header_without_hash: block_header.block_header_without_hash, + account_transaction_hashes, + l1_transaction_hashes, }) - .await? } async fn get_block_hash(&self, block_number: BlockNumber) -> StateSyncResult { - // Getting the next block because the Sync block only contains parent hash. - match (self.get_block(block_number).await, self.starknet_client.as_ref()) { - (Ok(block), _) => Ok(block.block_header_without_hash.parent_hash), - (Err(StateSyncError::BlockNotFound(_)), Some(starknet_client)) => { + // Attempt reading the block hash from the storage. + let storage_reader = self.storage_reader.clone(); + let block_hash_opt = Ok::<_, StateSyncError>( + storage_reader + .begin_ro_txn()? + .get_block_header(block_number)? + .map(|header| header.block_hash), + )?; + + match (block_hash_opt, self.starknet_client.as_ref()) { + (Some(block_hash), _) => Ok(block_hash), + (None, Some(starknet_client)) => { // As a fallback, try to get the block hash through the feeder directly. This // method is faster than get_block which the sync runner uses. - // TODO(shahak): Test this flow. starknet_client .block_hash(block_number) .await? .ok_or(StateSyncError::BlockNotFound(block_number)) } - (Err(err), _) => Err(err), + (None, None) => Err(StateSyncError::BlockNotFound(block_number)), } } @@ -177,20 +181,18 @@ impl StateSync { storage_key: StorageKey, ) -> StateSyncResult { let storage_reader = self.storage_reader.clone(); - tokio::task::spawn_blocking(move || { - let txn = storage_reader.begin_ro_txn()?; - verify_synced_up_to(&txn, block_number)?; - let state_number = StateNumber::unchecked_right_after_block(block_number); - let state_reader = txn.get_state_reader()?; + let txn = storage_reader.begin_ro_txn()?; + verify_synced_up_to(&txn, block_number)?; - verify_contract_deployed(&state_reader, state_number, contract_address)?; + let state_number = StateNumber::unchecked_right_after_block(block_number); + let state_reader = txn.get_state_reader()?; - let res = state_reader.get_storage_at(state_number, &contract_address, &storage_key)?; + verify_contract_deployed(&state_reader, state_number, contract_address)?; - Ok(res) - }) - .await? + let res = state_reader.get_storage_at(state_number, &contract_address, &storage_key)?; + + Ok(res) } async fn get_nonce_at( @@ -199,22 +201,20 @@ impl StateSync { contract_address: ContractAddress, ) -> StateSyncResult { let storage_reader = self.storage_reader.clone(); - tokio::task::spawn_blocking(move || { - let txn = storage_reader.begin_ro_txn()?; - verify_synced_up_to(&txn, block_number)?; - let state_number = StateNumber::unchecked_right_after_block(block_number); - let state_reader = txn.get_state_reader()?; + let txn = storage_reader.begin_ro_txn()?; + verify_synced_up_to(&txn, block_number)?; - verify_contract_deployed(&state_reader, state_number, contract_address)?; + let state_number = StateNumber::unchecked_right_after_block(block_number); + let state_reader = txn.get_state_reader()?; - let res = state_reader - .get_nonce_at(state_number, &contract_address)? - .ok_or(StateSyncError::ContractNotFound(contract_address))?; + verify_contract_deployed(&state_reader, state_number, contract_address)?; - Ok(res) - }) - .await? + let res = state_reader + .get_nonce_at(state_number, &contract_address)? + .ok_or(StateSyncError::ContractNotFound(contract_address))?; + + Ok(res) } async fn get_class_hash_at( @@ -223,28 +223,22 @@ impl StateSync { contract_address: ContractAddress, ) -> StateSyncResult { let storage_reader = self.storage_reader.clone(); - tokio::task::spawn_blocking(move || { - let txn = storage_reader.begin_ro_txn()?; - verify_synced_up_to(&txn, block_number)?; - - let state_number = StateNumber::unchecked_right_after_block(block_number); - let state_reader = txn.get_state_reader()?; - let class_hash = state_reader - .get_class_hash_at(state_number, &contract_address)? - .ok_or(StateSyncError::ContractNotFound(contract_address))?; - Ok(class_hash) - }) - .await? + let txn = storage_reader.begin_ro_txn()?; + verify_synced_up_to(&txn, block_number)?; + + let state_number = StateNumber::unchecked_right_after_block(block_number); + let state_reader = txn.get_state_reader()?; + let class_hash = state_reader + .get_class_hash_at(state_number, &contract_address)? + .ok_or(StateSyncError::ContractNotFound(contract_address))?; + Ok(class_hash) } async fn get_latest_block_number(&self) -> StateSyncResult> { let storage_reader = self.storage_reader.clone(); - tokio::task::spawn_blocking(move || { - let txn = storage_reader.begin_ro_txn()?; - let latest_block_number = latest_synced_block(&txn)?; - Ok(latest_block_number) - }) - .await? + let txn = storage_reader.begin_ro_txn()?; + let latest_block_number = latest_synced_block(&txn)?; + Ok(latest_block_number) } async fn is_class_declared_at( @@ -253,28 +247,25 @@ impl StateSync { class_hash: ClassHash, ) -> StateSyncResult { let storage_reader = self.storage_reader.clone(); - tokio::task::spawn_blocking(move || { - let class_definition_block_number_opt = storage_reader - .begin_ro_txn()? - .get_state_reader()? - .get_class_definition_block_number(&class_hash)?; - if let Some(class_definition_block_number) = class_definition_block_number_opt { - return Ok(class_definition_block_number <= block_number); - } + let class_definition_block_number_opt = storage_reader + .begin_ro_txn()? + .get_state_reader()? + .get_class_definition_block_number(&class_hash)?; + if let Some(class_definition_block_number) = class_definition_block_number_opt { + return Ok(class_definition_block_number <= block_number); + } - // TODO(noamsp): Add unit testing for cairo0 - let deprecated_class_definition_block_number_opt = storage_reader - .begin_ro_txn()? - .get_state_reader()? - .get_deprecated_class_definition_block_number(&class_hash)?; - - Ok(deprecated_class_definition_block_number_opt.is_some_and( - |deprecated_class_definition_block_number| { - deprecated_class_definition_block_number <= block_number - }, - )) - }) - .await? + // TODO(noamsp): Add unit testing for cairo0 + let deprecated_class_definition_block_number_opt = storage_reader + .begin_ro_txn()? + .get_state_reader()? + .get_deprecated_class_definition_block_number(&class_hash)?; + + Ok(deprecated_class_definition_block_number_opt.is_some_and( + |deprecated_class_definition_block_number| { + deprecated_class_definition_block_number <= block_number + }, + )) } } diff --git a/crates/apollo_state_sync/src/runner/mod.rs b/crates/apollo_state_sync/src/runner/mod.rs index ad55ea5fc97..0e061746bf4 100644 --- a/crates/apollo_state_sync/src/runner/mod.rs +++ b/crates/apollo_state_sync/src/runner/mod.rs @@ -19,7 +19,7 @@ use apollo_p2p_sync::client::{P2pSyncClient, P2pSyncClientChannels, P2pSyncClien use apollo_p2p_sync::server::{P2pSyncServer, P2pSyncServerChannels}; use apollo_p2p_sync::{Protocol, BUFFER_SIZE}; use apollo_p2p_sync_config::config::P2pSyncClientConfig; -use apollo_reverts::{revert_block, revert_blocks_and_eternal_pending}; +use apollo_reverts::{revert_block, revert_blocks_and_eternal_pending, RevertComponentData}; use apollo_rpc::{run_server, RpcConfig}; use apollo_starknet_client::reader::objects::pending_data::{ PendingBlock, @@ -35,6 +35,7 @@ use apollo_state_sync_metrics::metrics::{ P2P_SYNC_NUM_BLACKLISTED_PEERS, P2P_SYNC_NUM_CONNECTED_PEERS, STATE_SYNC_REVERTED_TRANSACTIONS, + STATE_SYNC_REVERTED_UP_TO_AND_INCLUDING, }; use apollo_state_sync_types::state_sync_types::SyncBlock; use apollo_storage::body::BodyStorageReader; @@ -183,6 +184,10 @@ impl StateSyncRunner { async {} }; + const STATE_SYNC_REVERT_COMPONENT_DATA: RevertComponentData = RevertComponentData { + name: "State Sync", + revert_metric: STATE_SYNC_REVERTED_UP_TO_AND_INCLUDING, + }; return ( Self { network_future: pending().boxed(), @@ -190,7 +195,7 @@ impl StateSyncRunner { current_header_marker, revert_up_to_and_including, revert_block_fn, - "State Sync", + &STATE_SYNC_REVERT_COMPONENT_DATA, ) .map(|_never| unreachable!("Never should never be constructed")) .boxed(), diff --git a/crates/apollo_state_sync/src/test.rs b/crates/apollo_state_sync/src/test.rs index 8e7266df3ec..d53932b727c 100644 --- a/crates/apollo_state_sync/src/test.rs +++ b/crates/apollo_state_sync/src/test.rs @@ -1,5 +1,7 @@ -// TODO(shahak): Test is_class_declared_at. +use std::sync::Arc; + use apollo_infra::component_definitions::ComponentRequestHandler; +use apollo_starknet_client::reader::{MockStarknetReader, StarknetReader}; use apollo_state_sync_types::communication::{StateSyncRequest, StateSyncResponse}; use apollo_state_sync_types::errors::StateSyncError; use apollo_storage::body::BodyStorageWriter; @@ -10,9 +12,11 @@ use apollo_storage::StorageWriter; use apollo_test_utils::{get_rng, get_test_block, get_test_state_diff, GetTestInstance}; use futures::channel::mpsc::channel; use indexmap::IndexMap; +use mockall::predicate; use rand_chacha::rand_core::RngCore; -use starknet_api::block::{Block, BlockHeader, BlockNumber}; +use starknet_api::block::{Block, BlockHash, BlockHeader, BlockNumber}; use starknet_api::core::{ClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkHash; use starknet_api::state::{StorageKey, ThinStateDiff}; use starknet_types_core::felt::Felt; @@ -72,9 +76,14 @@ async fn test_get_block() { async fn test_get_block_hash() { let (mut state_sync, mut storage_writer) = setup(); - let Block { header: expected_header, body: expected_body } = + let Block { header: mut expected_header, body: expected_body } = get_test_block(1, None, None, None); + // get_test_block returns a block with parent_hash == block_hash. Need to change that to make + // sure we don't return the parent hash + expected_header.block_hash.0 = + expected_header.block_header_without_hash.parent_hash.0 + Felt::from(1); + storage_writer .begin_rw_txn() .unwrap() @@ -102,6 +111,32 @@ async fn test_get_block_hash() { assert_eq!(block_hash, expected_header.block_hash); } +#[tokio::test] +async fn test_get_block_hash_fallback_to_starknet_client() { + let mut starknet_client = MockStarknetReader::new(); + let block_number = BlockNumber(100); + let expected_block_hash = BlockHash(StarkHash::from_hex_unchecked("0x123")); + starknet_client + .expect_block_hash() + .with(predicate::eq(block_number)) + .times(1) + .returning(move |_block_number| Ok(Some(expected_block_hash))); + + let starknet_client: Option> = + Some(Arc::new(starknet_client)); + let ((storage_reader, _storage_writer), _) = get_test_storage(); + let mut state_sync = + StateSync { storage_reader, new_block_sender: channel(0).0, starknet_client }; + + // The block is not in storage, so it should fall back to starknet_client + let response = state_sync.handle_request(StateSyncRequest::GetBlockHash(block_number)).await; + let StateSyncResponse::GetBlockHash(Ok(block_hash)) = response else { + panic!("Expected StateSyncResponse::GetBlockHash::Ok(_), but got {response:?}"); + }; + + assert_eq!(block_hash, expected_block_hash); +} + #[tokio::test] async fn test_get_storage_at() { let (mut state_sync, mut storage_writer) = setup(); diff --git a/crates/apollo_state_sync_metrics/src/metrics.rs b/crates/apollo_state_sync_metrics/src/metrics.rs index 3f39b3b37b3..f5e2c1daeac 100644 --- a/crates/apollo_state_sync_metrics/src/metrics.rs +++ b/crates/apollo_state_sync_metrics/src/metrics.rs @@ -5,7 +5,7 @@ use apollo_infra::metrics::{ RemoteClientMetrics, RemoteServerMetrics, }; -use apollo_metrics::{define_infra_metrics, define_metrics}; +use apollo_metrics::{define_infra_metrics, define_metrics, MetricCommon}; use apollo_state_sync_types::communication::STATE_SYNC_REQUEST_LABELS; use apollo_storage::body::BodyStorageReader; use apollo_storage::class_manager::ClassManagerStorageReader; @@ -39,6 +39,8 @@ define_metrics!( MetricGauge { STATE_SYNC_HEADER_LATENCY_SEC, "apollo_state_sync_header_latency", "The latency, in seconds, between a block timestamp (as state in its header) and the time the state sync component stores the header" }, MetricCounter { STATE_SYNC_PROCESSED_TRANSACTIONS, "apollo_state_sync_processed_transactions", "The number of transactions processed by the state sync component since its last restart", init = 0 }, MetricCounter { STATE_SYNC_REVERTED_TRANSACTIONS, "apollo_state_sync_reverted_transactions", "The number of transactions reverted by the state sync component", init = 0 }, + + MetricGauge { STATE_SYNC_REVERTED_UP_TO_AND_INCLUDING, "apollo_state_sync_reverted_up_to_and_including", "The block number up to which the state sync has reverted" }, }, ); @@ -55,6 +57,7 @@ pub async fn register_metrics( STATE_SYNC_REVERTED_TRANSACTIONS.register(); CENTRAL_SYNC_CENTRAL_BLOCK_MARKER.register(); CENTRAL_SYNC_FORKS_FROM_FEEDER.register(); + STATE_SYNC_REVERTED_UP_TO_AND_INCLUDING.register(); let txn = storage_reader.begin_ro_txn().unwrap(); update_marker_metrics(&txn); if should_replay_processed_txs_metric { diff --git a/crates/apollo_state_sync_types/Cargo.toml b/crates/apollo_state_sync_types/Cargo.toml index 2176df3821e..bd003c1eccb 100644 --- a/crates/apollo_state_sync_types/Cargo.toml +++ b/crates/apollo_state_sync_types/Cargo.toml @@ -26,7 +26,6 @@ starknet_api.workspace = true strum = { workspace = true, features = ["derive"] } strum_macros.workspace = true thiserror.workspace = true -tokio.workspace = true [dev-dependencies] mockall.workspace = true diff --git a/crates/apollo_state_sync_types/src/errors.rs b/crates/apollo_state_sync_types/src/errors.rs index 568cdf57f6b..c1ed2544831 100644 --- a/crates/apollo_state_sync_types/src/errors.rs +++ b/crates/apollo_state_sync_types/src/errors.rs @@ -6,7 +6,6 @@ use starknet_api::block::BlockNumber; use starknet_api::core::{ClassHash, ContractAddress}; use starknet_api::StarknetApiError; use thiserror::Error; -use tokio::task::JoinError; #[derive(Debug, Error, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum StateSyncError { @@ -32,8 +31,6 @@ pub enum StateSyncError { EmptyState, #[error("Error while trying to communicate with feeder gateway: {0}")] ReaderClientError(String), - #[error("Error while trying to join a task: {0}")] - JoinError(String), } impl From for StateSyncError { @@ -59,9 +56,3 @@ impl From for StateSyncError { StateSyncError::ReaderClientError(error.to_string()) } } - -impl From for StateSyncError { - fn from(error: JoinError) -> Self { - StateSyncError::JoinError(error.to_string()) - } -} diff --git a/crates/apollo_storage/Cargo.toml b/crates/apollo_storage/Cargo.toml index e677a8a0f7e..b70501e2ef1 100644 --- a/crates/apollo_storage/Cargo.toml +++ b/crates/apollo_storage/Cargo.toml @@ -55,8 +55,10 @@ apollo_test_utils.workspace = true assert_matches.workspace = true cairo-lang-casm = { workspace = true, features = ["parity-scale-codec", "schemars"] } camelpaste.workspace = true +futures.workspace = true insta = { workspace = true, features = ["yaml"] } metrics-exporter-prometheus.workspace = true +nix.workspace = true num-traits.workspace = true paste.workspace = true pretty_assertions.workspace = true diff --git a/crates/apollo_storage/README.md b/crates/apollo_storage/README.md index e58a8e50901..cc380f32876 100644 --- a/crates/apollo_storage/README.md +++ b/crates/apollo_storage/README.md @@ -1,5 +1,5 @@ -# papyrus-storage +# apollo-storage ## Description -papyrus-storage provides a writing and reading interface for various Starknet data structures to a database, designed specifically for Papyrus, a Starknet node. +apollo-storage provides a writing and reading interface for various Starknet data structures to a database, designed specifically for Apollo, a Starknet sequencer. diff --git a/crates/apollo_storage/src/db/mod.rs b/crates/apollo_storage/src/db/mod.rs index 282a482e4dc..82a04e5840f 100644 --- a/crates/apollo_storage/src/db/mod.rs +++ b/crates/apollo_storage/src/db/mod.rs @@ -222,6 +222,8 @@ pub(crate) fn open_env(config: &DbConfig) -> DbResult<(DbReader, DbWriter)> { no_rdahead: true, // LIFO policy for recycling a Garbage Collection items should be faster. liforeclaim: true, + // Exclusive access - prevent other processes from opening the same database. + exclusive: true, ..Default::default() }) .open(&config.path())?, diff --git a/crates/apollo_storage/src/lib.rs b/crates/apollo_storage/src/lib.rs index c40616c2a06..3011dc2aef7 100644 --- a/crates/apollo_storage/src/lib.rs +++ b/crates/apollo_storage/src/lib.rs @@ -102,6 +102,9 @@ mod deprecated; #[cfg(test)] mod test_instances; +#[cfg(test)] +mod open_storage_test; + #[cfg(any(feature = "testing", test))] pub mod test_utils; diff --git a/crates/apollo_storage/src/open_storage_test.rs b/crates/apollo_storage/src/open_storage_test.rs new file mode 100644 index 00000000000..503d6060644 --- /dev/null +++ b/crates/apollo_storage/src/open_storage_test.rs @@ -0,0 +1,158 @@ +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use starknet_api::block::BlockNumber; +use tempfile::tempdir; + +use crate::header::HeaderStorageReader; +use crate::test_utils::get_test_config_with_path; +use crate::{open_storage, StorageConfig, StorageError, StorageReader, StorageScope}; + +/// Check that storage reader can access storage +fn check_storage_is_accessible(reader: &StorageReader) -> bool { + reader.begin_ro_txn().unwrap().get_block_signature(BlockNumber(0)).unwrap().is_none() +} + +/// Test that opening storage twice in the same thread fails. +/// +/// This test verifies that attempting to open the same storage database twice +/// within a single thread results in an libmdbx error. +/// +/// # Test Flow +/// 1. Opens storage successfully the first time +/// 2. Verifies the storage is accessible and empty +/// 3. Attempts to open the same storage again (should fail) +/// 4. Asserts that the second attempt returns `StorageError::InnerError` +#[test] +fn get_storage_twice_should_fail() { + let temp_dir = tempdir().expect("Failed to create temp directory"); + let config = + get_test_config_with_path(Some(StorageScope::StateOnly), temp_dir.path().to_path_buf()); + + // Get storage first time + let (reader, mut _writer) = open_storage(config.clone()).unwrap(); + assert!(check_storage_is_accessible(&reader)); + + // Get the same storage second time should fail because tables already exist + let result = open_storage(config); + assert!( + matches!(result, Err(StorageError::InnerError(_))), + "Opening storage twice should fail" + ); +} + +/// Test that opening storage from two threads fails. +/// +/// This test verifies that when two threads attempt to open the same storage +/// database concurrently, only one succeeds while the other fails with an error. +/// Uses thread synchronization via barriers to ensure both threads attempt +/// storage access simultaneously. +/// +/// # Test Flow +/// 1. Creates two threads that will attempt to open storage +/// 2. Uses `std::sync::Barrier` to synchronize thread execution +/// 3. First thread opens storage immediately +/// 4. Second thread waits 100 milliseconds then attempts to open storage +/// 5. Both threads synchronize at barrier after their attempts +/// 6. Verifies that first thread succeeds and second thread fails +#[test] +fn get_storage_from_two_threads_should_fail() { + let temp_dir = tempdir().expect("Failed to create temp directory"); + let config = + get_test_config_with_path(Some(StorageScope::StateOnly), temp_dir.path().to_path_buf()); + let barrier = Arc::new(std::sync::Barrier::new(2)); + + // Start both threads + let config1 = config.clone(); + let barrier1 = barrier.clone(); + let handle1 = thread::spawn(move || open_storage_with_barrier(config1, barrier1)); + + let handle2 = { + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + open_storage_with_barrier(config, barrier) + }) + }; + + // Wait for both threads to complete + let result1 = handle1.join().unwrap(); + let result2 = handle2.join().unwrap(); + assert!( + result1.is_ok() && matches!(result2, Err(StorageError::InnerError(_))), + "Opening storage from two threads should fail" + ); +} + +/// Function to handle storage opening with barrier synchronization. +fn open_storage_with_barrier( + config: StorageConfig, + barrier: Arc, +) -> Result<(), StorageError> { + let result = open_storage(config); + barrier.wait(); // Synchronize with other thread. + match result { + Ok((reader, _writer)) => { + assert!(check_storage_is_accessible(&reader)); + Ok(()) + } + Err(e) => Err(e), + } +} + +/// Test that opening storage from two async tokio tasks fails. +/// +/// This test verifies that when two async tasks attempt to open the same storage +/// database concurrently, only one succeeds while the other fails with an error. +/// Uses tokio's async barrier synchronization to coordinate task execution. +/// +/// # Test Flow +/// 1. Creates two async tasks that will attempt to open storage +/// 2. Uses `tokio::sync::Barrier` to synchronize task execution +/// 3. First task opens storage immediately +/// 4. Second task waits 100 milliseconds then attempts to open storage +/// 5. Both tasks synchronize at barrier after their attempts +/// 6. Verifies that first task succeeds and second task fails +#[tokio::test] +async fn get_storage_from_two_tokio_tasks_should_fail() { + let temp_dir = tempdir().expect("Failed to create temp directory"); + let config = + get_test_config_with_path(Some(StorageScope::StateOnly), temp_dir.path().to_path_buf()); + + let barrier = Arc::new(tokio::sync::Barrier::new(2)); + + let config1 = config.clone(); + let barrier1 = barrier.clone(); + let task1 = + tokio::spawn(async move { async_open_storage_with_barrier(config1, barrier1).await }); + + let task2 = tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(100)).await; + async_open_storage_with_barrier(config, barrier).await + }); + + let results = tokio::join!(task1, task2); + + let task1_result = results.0.unwrap(); + let task2_result = results.1.unwrap(); + assert!( + task1_result.is_ok() && matches!(task2_result, Err(StorageError::InnerError(_))), + "Opening storage from two tokio tasks should fail" + ); +} + +/// Function to handle storage opening with barrier synchronization +async fn async_open_storage_with_barrier( + config: StorageConfig, + barrier: Arc, +) -> Result<(), StorageError> { + let result = open_storage(config); + barrier.wait().await; // Synchronize with other thread + match result { + Ok((reader, _writer)) => { + assert!(check_storage_is_accessible(&reader)); + Ok(()) + } + Err(e) => Err(e), + } +} diff --git a/crates/apollo_storage/tests/open_storage_in_processes_test.rs b/crates/apollo_storage/tests/open_storage_in_processes_test.rs new file mode 100644 index 00000000000..1207e96f30d --- /dev/null +++ b/crates/apollo_storage/tests/open_storage_in_processes_test.rs @@ -0,0 +1,158 @@ +// This test is used to test the storage opening in two separate processes. +// It runs as an integration test because forking a process within a unit test acts unexpectedly. + +use std::fs::{self, create_dir_all}; +use std::path::PathBuf; +use std::thread; +use std::time::{Duration, Instant}; + +use apollo_storage::db::DbConfig; +use apollo_storage::header::HeaderStorageReader; +use apollo_storage::mmap_file::MmapFileConfig; +use apollo_storage::{open_storage, StorageConfig, StorageError, StorageReader, StorageScope}; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use nix::unistd::{fork, ForkResult}; +use starknet_api::block::BlockNumber; +use starknet_api::test_utils::CHAIN_ID_FOR_TESTS; +use tempfile::tempdir; + +/// Returns a db config for a given path. +/// This function ensures that the specified directory exists by creating it if necessary. +/// Note: This function is a copy from the private test_utils.rs module. +fn get_test_config_with_path(storage_scope: Option, path: PathBuf) -> StorageConfig { + let storage_scope = storage_scope.unwrap_or_default(); + create_dir_all(&path).expect("Failed to create directory"); + + StorageConfig { + db_config: DbConfig { + path_prefix: path, + chain_id: CHAIN_ID_FOR_TESTS.clone(), + enforce_file_exists: false, + min_size: 1 << 20, // 1MB + max_size: 1 << 35, // 32GB + growth_step: 1 << 26, // 64MB + max_readers: 1 << 13, // 8K readers + }, + scope: storage_scope, + mmap_file_config: MmapFileConfig { + max_size: 1 << 24, // 16MB + growth_step: 1 << 20, // 1MB + max_object_size: 1 << 16, // 64KB + }, + } +} + +/// Check that storage reader can access storage +fn check_storage_is_accessible(reader: &StorageReader) -> bool { + reader.begin_ro_txn().unwrap().get_block_signature(BlockNumber(0)).unwrap().is_none() +} + +/// Helper function to wait for a child process with a timeout. +/// +/// Polls `waitpid` with `WNOHANG` flag until either: +/// - The child exits (returns exit status) +/// - The timeout is reached (returns None) +fn waitpid_with_timeout(pid: nix::unistd::Pid, timeout: Duration) -> Option { + let start = Instant::now(); + loop { + match waitpid(pid, Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::Exited(_, exit_code)) => return Some(exit_code), + Ok(WaitStatus::StillAlive) => { + if start.elapsed() >= timeout { + return None; + } + thread::sleep(Duration::from_millis(10)); + } + Ok(_) => return None, // Other status like signaled, stopped, etc. + Err(_) => return None, + } + } +} + +/// Test that opening storage from two separate processes fails. +/// +/// This test verifies that the libmdbx database exclusive locking mechanism works correctly +/// across process boundaries. When one process has opened the storage with exclusive access, +/// any subsequent attempt by another process to open the same storage should fail. +/// +/// # Test Flow +/// 1. **Fork Process**: Creates a parent and child process using `fork()` +/// 2. **Parent Process**: +/// - Opens storage successfully (gets exclusive lock) +/// - Verifies storage is accessible and empty +/// - Signals child process to proceed +/// - Waits for child to complete its attempt +/// - Verifies child process exited with error code 1 +/// 3. **Child Process**: +/// - Waits for parent to signal readiness +/// - Attempts to open the same storage (should fail) +/// - Expects `StorageError::InnerError` due to MDBX exclusive lock +/// - Exits with code 1 (indicating expected failure) +/// +/// # Synchronization +/// Uses file-based synchronization between processes: +/// - `parent_ready`: Created when parent has opened storage +#[test] +fn get_storage_from_two_processes_with_fork_should_fail() { + let temp_dir = tempdir().expect("Failed to create temp directory"); + let config = + get_test_config_with_path(Some(StorageScope::StateOnly), temp_dir.path().to_path_buf()); + + // Use regular files for synchronization between processes + let parent_ready_path = temp_dir.path().join("parent_ready"); + + match unsafe { fork() } { + Ok(ForkResult::Parent { child }) => { + // Parent process + let (reader, _writer) = open_storage(config.clone()).unwrap(); + assert!(check_storage_is_accessible(&reader)); + + // Signal child that parent has opened storage + fs::write(&parent_ready_path, b"1").unwrap(); + + // Wait for child process to complete with timeout + match waitpid_with_timeout(child, Duration::from_secs(5)) { + Some(exit_code) => { + assert!( + exit_code == 1, + "Parent: Child process should exit with exit code 1, received: {exit_code}" + ); + } + None => { + panic!("Parent: Timeout or error waiting for child process to exit"); + } + } + + // Clean up + let _ = fs::remove_file(&parent_ready_path); + } + Ok(ForkResult::Child) => { + // Wait for parent to signal that it has opened storage + let timeout = Instant::now() + Duration::from_secs(5); + while !parent_ready_path.exists() { + if Instant::now() >= timeout { + println!("Child: Timeout waiting for parent to be ready"); + std::process::exit(2); + } + thread::sleep(Duration::from_millis(10)); + } + + // Child process - try to open storage + let result = open_storage(config); + let exit_code = match result { + Ok((_reader, _writer)) => { + println!("Child: Unexpected success opening storage"); + 0 + } + Err(StorageError::InnerError(_)) => 1, + Err(e) => { + println!("Child: Storage opening failed with unexpected error: {e:?}"); + 2 + } + }; + + std::process::exit(exit_code); + } + Err(_) => panic!("Fork failed"), + } +} diff --git a/crates/apollo_time/src/time.rs b/crates/apollo_time/src/time.rs index ae4575ab8a5..d18c9447e9a 100644 --- a/crates/apollo_time/src/time.rs +++ b/crates/apollo_time/src/time.rs @@ -18,19 +18,27 @@ pub trait Clock: Send + Sync + Debug { } } -/// Free function to sleep until a given deadline using a provided clock. -#[cfg(feature = "tokio")] -pub async fn sleep_until(deadline: DateTime, clock: &dyn Clock) { - // Calculate how much time is left until the deadline. - // If the deadline has already passed, this will be a negative duration. - let time_delta = deadline - clock.now(); - // Convert to `std::time::Duration`, clamping any negative value to zero. - // A zero-duration sleep is effectively a no-op. - let duration_to_sleep = time_delta.to_std().unwrap_or_default(); - // Sleep for the computed duration (or return immediately if zero). - tokio::time::sleep(duration_to_sleep).await; +pub trait ClockExt: Clock { + #[cfg(feature = "tokio")] + fn sleep_until<'a>( + &'a self, + deadline: DateTime, + ) -> impl core::future::Future + Send + 'a { + async move { + // Calculate how much time is left until the deadline. + // If the deadline has already passed, this will be a negative duration. + let time_delta = deadline - self.now(); + // Convert to `std::time::Duration`, clamping any negative value to zero. + // A zero-duration sleep is effectively a no-op. + let duration_to_sleep = time_delta.to_std().unwrap_or_default(); + // Sleep for the computed duration (or return immediately if zero). + tokio::time::sleep(duration_to_sleep).await; + } + } } +impl ClockExt for T {} + #[derive(Clone, Copy, Debug, Default)] pub struct DefaultClock; diff --git a/crates/bench_tools/Cargo.toml b/crates/bench_tools/Cargo.toml new file mode 100644 index 00000000000..2654db5e7a2 --- /dev/null +++ b/crates/bench_tools/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bench_tools" +edition.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +clap = { workspace = true, features = ["derive"] } +criterion.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true + +[dev-dependencies] +apollo_infra_utils.workspace = true +glob.workspace = true +rstest.workspace = true +serde_json.workspace = true +tempfile.workspace = true + +[[bench]] +harness = false +name = "dummy_bench" +path = "src/benches/dummy_bench.rs" diff --git a/crates/bench_tools/README.md b/crates/bench_tools/README.md new file mode 100644 index 00000000000..8ebe2854426 --- /dev/null +++ b/crates/bench_tools/README.md @@ -0,0 +1,159 @@ +# bench_tools + +Benchmark framework for running Criterion benchmarks with CI integration, automated input management via GCS, and performance regression detection. + +## Quick Start + +```bash +# List available benchmarks +cargo run -p bench_tools -- list + +# Run benchmarks for a package +cargo run -p bench_tools -- run --package starknet_committer_and_os_cli --out ./results + +# Run with regression checks (requires baseline: cargo bench -p ) +cargo run -p bench_tools -- run-and-compare \ + --package starknet_committer_and_os_cli \ + --out ./results \ + --regression-limit 5.0 +``` + +## CLI Commands + +### `run` + +Execute benchmarks and save results. + +```bash +cargo run -p bench_tools -- run --package --out [--input-dir ] +``` + +- Downloads inputs from GCS automatically, or use `--input-dir` for local inputs +- Saves results to output directory as `{bench_name}_estimates.json` + +### `run-and-compare` + +Execute benchmarks and fail if regressions exceed limits. +Supports both relative (percentage change) and absolute (time in nanoseconds) limits. + +**Limits:** +- `--regression-limit`: Required. Maximum acceptable percentage change vs +baseline (e.g., 5.0 = 5%). +- `--set-absolute-time-ns-limit`: Optional. Can be specified multiple +times to set absolute time thresholds for specific benchmarks. + + +```bash +cargo run -p bench_tools -- run-and-compare \ + --package \ + --out \ + --regression-limit 5.0 \ + [--set-absolute-time-ns-limit ] +``` + +Example with absolute time limits: + +```bash +cargo run -p bench_tools -- run-and-compare \ + --package starknet_committer_and_os_cli \ + --out ./results \ + --regression-limit 5.0 \ + --set-absolute-time-ns-limit full_committer_flow 50000000 \ + --set-absolute-time-ns-limit tree_computation_flow 30000000 +``` + +Output: +``` +✓ full_committer_flow: +2.34% | 45123456.78ns +✓ tree_computation_flow: -0.89% | 28765432.10ns +✅ All benchmarks passed regression check! +``` + +Or if exceeded: +- relative limit: +``` +❌ full_committer_flow: +7.50% (EXCEEDS 5.0% limit) +``` +- absolute limit: +``` +❌ tree_computation_flow: 28765432.10ns (EXCEEDS 28765400ns limit) +``` + +### `list` + +Show available benchmarks. + +```bash +cargo run -p bench_tools -- list [--package ] +``` + +### `upload-inputs` + +Upload benchmark inputs to GCS. Authenticate with `gcloud auth login`. + +```bash +cargo run -p bench_tools -- upload-inputs \ + --benchmark \ + --input-dir +``` + +## Adding New Benchmarks + +Add a `BenchmarkConfig` to the `BENCHMARKS` array in `src/types/benchmark_config.rs`: + +```rust +BenchmarkConfig { + name: "my_benchmark", // Unique identifier + package: "my_package", // Cargo package name + cmd_args: &["bench", "-p", "my_package", "pattern"], // cargo bench args + input_dir: Some("crates/my_package/test_inputs"), // Optional, for GCS inputs + criterion_benchmark_names: None, // None = single bench with same name +}, +``` + +### Multiple Criterion Benchmarks + +If your benchmark file has multiple `bench_function` calls: + +```rust +BenchmarkConfig { + name: "multi_bench", + package: "my_package", + cmd_args: &["bench", "-p", "my_package", "--bench", "my_bench"], + input_dir: None, + criterion_benchmark_names: Some(&["bench_1", "bench_2", "bench_3"]), +}, +``` + +### Field Reference + +| Field | Description | +|-------|-------------| +| `name` | Config identifier (for CLI) | +| `package` | Cargo package containing the benchmark | +| `cmd_args` | Arguments for `cargo bench` | +| `input_dir` | Where to place downloaded inputs (if needed) | +| `criterion_benchmark_names` | List of Criterion bench names (for regression checks). `None` = uses `name` | + +### Steps + +1. Write benchmark in your package's `benches/` directory +2. Add `[[bench]]` section to package's `Cargo.toml` +3. Add `BenchmarkConfig` to `BENCHMARKS` array +4. If inputs needed: `cargo run -p bench_tools -- upload-inputs --benchmark --input-dir ` +5. Run: `cargo run -p bench_tools -- run --package --out ./results` + +## GCS Integration + +Inputs are stored at `gs://apollo_benchmarks/{benchmark_name}/input/`. Authenticate with `gcloud auth login`. + +- Inputs auto-download if `input_dir` is set and no `--input-dir` provided +- Use `--input-dir` to override with local inputs +- Use `upload-inputs` command to push new inputs to GCS + +## Notes + +- First run establishes baseline: `cargo bench -p ` +- Subsequent `run-and-compare` compares against baseline +- Positive % = slower (regression), Negative % = faster (improvement) +- See `src/benches/dummy_bench.rs` for example implementation diff --git a/crates/bench_tools/data/dummy_bench_input/large_input.json b/crates/bench_tools/data/dummy_bench_input/large_input.json new file mode 100644 index 00000000000..9b5f02e0c0c --- /dev/null +++ b/crates/bench_tools/data/dummy_bench_input/large_input.json @@ -0,0 +1,105 @@ +{ + "values": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100 + ], + "multiplier": 10 +} \ No newline at end of file diff --git a/crates/bench_tools/data/dummy_bench_input/small_input.json b/crates/bench_tools/data/dummy_bench_input/small_input.json new file mode 100644 index 00000000000..591aebffbb5 --- /dev/null +++ b/crates/bench_tools/data/dummy_bench_input/small_input.json @@ -0,0 +1,15 @@ +{ + "values": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "multiplier": 2 +} \ No newline at end of file diff --git a/crates/bench_tools/data/dummy_benches_result/dummy_sum_1000_estimates.json b/crates/bench_tools/data/dummy_benches_result/dummy_sum_1000_estimates.json new file mode 100644 index 00000000000..07cffaf9d0e --- /dev/null +++ b/crates/bench_tools/data/dummy_benches_result/dummy_sum_1000_estimates.json @@ -0,0 +1,47 @@ +{ + "mean": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.4627450463077952, + "upper_bound": 0.4672721018359347 + }, + "point_estimate": 0.464843138908376, + "standard_error": 0.001159579008842323 + }, + "median": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.46018939259554725, + "upper_bound": 0.4630457711597157 + }, + "point_estimate": 0.46110773204298744, + "standard_error": 0.0007700792406655315 + }, + "median_abs_dev": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.004389190374631315, + "upper_bound": 0.008068153741443406 + }, + "point_estimate": 0.0057180067999875826, + "standard_error": 0.0008869433017250109 + }, + "slope": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.4613537171491014, + "upper_bound": 0.4679905568783797 + }, + "point_estimate": 0.4641046136274776, + "standard_error": 0.001730736547763549 + }, + "std_dev": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.007242933241527838, + "upper_bound": 0.01557482754536567 + }, + "point_estimate": 0.01165848802634422, + "standard_error": 0.00216291128423961 + } +} \ No newline at end of file diff --git a/crates/bench_tools/data/dummy_benches_result/dummy_sum_100_estimates.json b/crates/bench_tools/data/dummy_benches_result/dummy_sum_100_estimates.json new file mode 100644 index 00000000000..5e3be72f309 --- /dev/null +++ b/crates/bench_tools/data/dummy_benches_result/dummy_sum_100_estimates.json @@ -0,0 +1,47 @@ +{ + "mean": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.45957162353881537, + "upper_bound": 0.47252808549400704 + }, + "point_estimate": 0.4656174818033215, + "standard_error": 0.0033071130641006297 + }, + "median": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.45151445843970833, + "upper_bound": 0.4538904649825635 + }, + "point_estimate": 0.4526323188039608, + "standard_error": 0.0006992971024409143 + }, + "median_abs_dev": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.005240482428601258, + "upper_bound": 0.010332193401481504 + }, + "point_estimate": 0.007727865411134973, + "standard_error": 0.001301158952978215 + }, + "slope": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.455400693078925, + "upper_bound": 0.46369875851255066 + }, + "point_estimate": 0.45933814542565166, + "standard_error": 0.002119888457921763 + }, + "std_dev": { + "confidence_interval": { + "confidence_level": 0.95, + "lower_bound": 0.02332892198500483, + "upper_bound": 0.041110549300589966 + }, + "point_estimate": 0.03317521516535013, + "standard_error": 0.004530135795169152 + } +} \ No newline at end of file diff --git a/crates/bench_tools/src/benches.rs b/crates/bench_tools/src/benches.rs new file mode 100644 index 00000000000..a2cd56b672b --- /dev/null +++ b/crates/bench_tools/src/benches.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +pub(crate) mod dummy_bench; diff --git a/crates/bench_tools/src/benches/dummy_bench.rs b/crates/bench_tools/src/benches/dummy_bench.rs new file mode 100644 index 00000000000..298dfcc09f9 --- /dev/null +++ b/crates/bench_tools/src/benches/dummy_bench.rs @@ -0,0 +1,53 @@ +use std::hint::black_box; + +use criterion::{criterion_group, criterion_main, Criterion}; +use serde::{Deserialize, Serialize}; + +// Input files are embedded at compile time. +// Before building, ensure these files exist (e.g., downloaded from GCS). +const SMALL_INPUT: &str = include_str!("../../data/dummy_bench_input/small_input.json"); +const LARGE_INPUT: &str = include_str!("../../data/dummy_bench_input/large_input.json"); + +#[derive(Debug, Serialize, Deserialize)] +struct DummyInput { + values: Vec, + multiplier: u64, +} + +#[allow(dead_code)] +fn dummy_function(n: u64) -> u64 { + // Simple function that does some work + (0..n).sum() +} + +/// Example benchmark functions to demonstrate how to use Criterion for benchmarking. +/// This is used to test the benchmarking infrastructure and generate sample benchmark results +/// that can be parsed by the bench_tools framework. +#[allow(dead_code)] +fn dummy_benchmark(c: &mut Criterion) { + // black_box prevents the compiler from optimizing away the function call during benchmarking + c.bench_function("dummy_sum_100", |b| b.iter(|| black_box(dummy_function(100)))); + + c.bench_function("dummy_sum_1000", |b| b.iter(|| black_box(dummy_function(1000)))); +} + +#[allow(dead_code)] +fn dummy_benchmark_with_input(c: &mut Criterion) { + let process_input = |input: &DummyInput| input.values.iter().sum::() * input.multiplier; + + let small_input: DummyInput = serde_json::from_str(SMALL_INPUT).unwrap(); + let large_input: DummyInput = serde_json::from_str(LARGE_INPUT).unwrap(); + + c.bench_function("dummy_process_small_input", |b| { + // black_box prevents the compiler from optimizing away the function call during + // benchmarking + b.iter(|| black_box(process_input(&small_input))) + }); + + c.bench_function("dummy_process_large_input", |b| { + b.iter(|| black_box(process_input(&large_input))) + }); +} + +criterion_group!(benches, dummy_benchmark, dummy_benchmark_with_input); +criterion_main!(benches); diff --git a/crates/bench_tools/src/comparison.rs b/crates/bench_tools/src/comparison.rs new file mode 100644 index 00000000000..d8ae1ad25c0 --- /dev/null +++ b/crates/bench_tools/src/comparison.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +use crate::types::estimates::Estimates; + +/// Result of a benchmark comparison. +#[derive(Debug)] +pub struct BenchmarkComparison { + pub name: String, + pub change_percentage: f64, + pub exceeds_regression_limit: bool, + pub absolute_time_ns: f64, + pub exceeds_absolute_limit: bool, +} + +type RegressionError = (String, Vec); +type BenchmarkComparisonsResult = Result, RegressionError>; + +/// Loads change estimates from criterion's change directory for a given benchmark. +/// Panics if the change file doesn't exist. +fn load_change_estimates(bench_name: &str) -> Estimates { + let change_path = + PathBuf::from("target/criterion").join(bench_name).join("change/estimates.json"); + + if !change_path.exists() { + panic!( + "Change file not found for benchmark '{}': {}\nThis likely means no baseline exists. \ + Run the benchmark at least once before using run-and-compare.", + bench_name, + change_path.display() + ); + } + + let data = fs::read_to_string(&change_path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", change_path.display(), e)); + + serde_json::from_str(&data).unwrap_or_else(|e| { + panic!("Failed to deserialize {}: {}\nContent: {}", change_path.display(), e, data) + }) +} + +/// Loads absolute timing estimates from criterion's new directory for a given benchmark. +/// Panics if the estimates file doesn't exist. +fn load_absolute_estimates(bench_name: &str) -> Estimates { + let estimates_path = + PathBuf::from("target/criterion").join(bench_name).join("new/estimates.json"); + + if !estimates_path.exists() { + panic!( + "Estimates file not found for benchmark '{}': {}\nThis likely means the benchmark \ + hasn't been run yet. Run the benchmark before using comparison features.", + bench_name, + estimates_path.display() + ); + } + + let data = fs::read_to_string(&estimates_path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", estimates_path.display(), e)); + + serde_json::from_str(&data).unwrap_or_else(|e| { + panic!("Failed to deserialize {}: {}\nContent: {}", estimates_path.display(), e, data) + }) +} + +/// Converts change estimates to percentage. +/// The mean.point_estimate in change/estimates.json represents fractional change +/// (e.g., 0.0706 = 7.06% change). +pub(crate) fn get_regression_percentage(change_estimates: &Estimates) -> f64 { + change_estimates.mean.point_estimate * 100.0 +} + +/// Checks all benchmarks for regressions against a specified limit. +/// Returns a vector of comparison results for all benchmarks. +/// If any benchmark exceeds the regression limit or absolute time threshold, returns an error with +/// detailed results. Panics if change file is not found for any benchmark. +pub fn check_regressions( + bench_names: &[&str], + regression_limit: f64, + absolute_time_ns_limits: &HashMap, +) -> BenchmarkComparisonsResult { + let mut results = Vec::new(); + let mut exceeded_count = 0; + + for bench_name in bench_names { + let change_estimates = load_change_estimates(bench_name); + let change_percentage = get_regression_percentage(&change_estimates); + let exceeds_regression_limit = change_percentage > regression_limit; + + // Load absolute timing estimates. + let absolute_estimates = load_absolute_estimates(bench_name); + let absolute_time_ns = absolute_estimates.mean.point_estimate; + + // Check if this benchmark has a specific absolute time limit. + let exceeds_absolute_limit = + if let Some(&threshold) = absolute_time_ns_limits.get(*bench_name) { + absolute_time_ns > threshold + } else { + false + }; + + if exceeds_regression_limit || exceeds_absolute_limit { + exceeded_count += 1; + } + + results.push(BenchmarkComparison { + name: bench_name.to_string(), + change_percentage, + exceeds_regression_limit, + absolute_time_ns, + exceeds_absolute_limit, + }); + } + + if exceeded_count > 0 { + let error_msg = format!("{} benchmark(s) exceeded threshold(s)!", exceeded_count); + Err((error_msg, results)) + } else { + Ok(results) + } +} diff --git a/crates/bench_tools/src/gcs.rs b/crates/bench_tools/src/gcs.rs new file mode 100644 index 00000000000..4cc61e95cf2 --- /dev/null +++ b/crates/bench_tools/src/gcs.rs @@ -0,0 +1,69 @@ +use std::path::Path; +use std::process::Command; + +/// Default GCS bucket for benchmarks. +pub const BENCHMARKS_BUCKET: &str = "apollo_benchmarks"; + +/// Uploads all files from a local directory to Google Cloud Storage. +/// +/// Uses gcloud CLI to upload files. Before running, authenticate with: +/// `gcloud auth login` +/// +/// Files are uploaded to: `gs://{BENCHMARKS_BUCKET}/{benchmark_name}/input/` +pub fn upload_inputs(benchmark_name: &str, input_dir: &Path) { + println!( + "Uploading inputs from {} to gs://{}/{}/input/", + input_dir.display(), + BENCHMARKS_BUCKET, + benchmark_name + ); + + let source = format!("{}/*", input_dir.display()); + let dest = format!("gs://{}/{}/input/", BENCHMARKS_BUCKET, benchmark_name); + + // Use gcloud storage cp command to upload files. + let output = Command::new("gcloud") + .args(["storage", "cp", "-r", &source, &dest]) + .output() + .expect("Failed to upload inputs to GCS"); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("Failed to upload inputs to GCS: {}", stderr); + } + + println!("{}", String::from_utf8_lossy(&output.stdout).trim()); + println!("Input files uploaded successfully!"); +} + +/// Downloads all input files for a benchmark from Google Cloud Storage. +/// +/// Uses gcloud CLI to download files. Before running, authenticate with: +/// `gcloud auth login` +/// +/// Downloads from: `gs://{BENCHMARKS_BUCKET}/{benchmark_name}/input/` to the local input directory. +pub fn download_inputs(benchmark_name: &str, local_input_dir: &Path) { + println!( + "Downloading inputs from gs://{}/{}/input/ to {}", + BENCHMARKS_BUCKET, + benchmark_name, + local_input_dir.display() + ); + + let source = format!("gs://{}/{}/input/*", BENCHMARKS_BUCKET, benchmark_name); + let dest = local_input_dir.display().to_string(); + + // Use gcloud storage cp command to download files. + let output = Command::new("gcloud") + .args(["storage", "cp", "-r", &source, &dest]) + .output() + .unwrap_or_else(|e| panic!("Failed to cp inputs from GCS: {}", e)); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("Failed to download inputs from GCS: {}", stderr); + } + + println!("{}", String::from_utf8_lossy(&output.stdout).trim()); + println!("Input files downloaded successfully!"); +} diff --git a/crates/bench_tools/src/gcs_test.rs b/crates/bench_tools/src/gcs_test.rs new file mode 100644 index 00000000000..091d157602e --- /dev/null +++ b/crates/bench_tools/src/gcs_test.rs @@ -0,0 +1,48 @@ +use std::fs; + +use tempfile::TempDir; + +use crate::gcs::{download_inputs, upload_inputs}; +use crate::test_utils; + +#[test] +#[ignore] // Run with: cargo test -p bench_tools -- --ignored +fn test_upload_and_download_inputs() { + let benchmark_name = "dummy_benchmark"; + + // Get paths relative to bench_tools crate directory. + let source_dir = test_utils::bench_tools_crate_dir().join("data/dummy_bench_input"); + + // Ensure source files exist. + assert!(source_dir.exists(), "Source directory does not exist: {}", source_dir.display()); + + // Upload inputs. + println!("Testing upload..."); + upload_inputs(benchmark_name, &source_dir); + + // Create temp directory for download. + let temp_dir = TempDir::new().unwrap(); + let download_dir = temp_dir.path(); + + println!("\nDownload directory: {}", download_dir.display()); + + // Download inputs to temp directory. + println!("\nTesting download..."); + download_inputs(benchmark_name, download_dir); + + // Verify files were downloaded. + let small_input = download_dir.join("small_input.json"); + let large_input = download_dir.join("large_input.json"); + + assert!(small_input.exists(), "small_input.json was not downloaded"); + assert!(large_input.exists(), "large_input.json was not downloaded"); + + // Verify content matches original. + let original_small = fs::read_to_string(source_dir.join("small_input.json")).unwrap(); + let downloaded_small = fs::read_to_string(&small_input).unwrap(); + assert_eq!(original_small, downloaded_small, "small_input.json content does not match"); + + let original_large = fs::read_to_string(source_dir.join("large_input.json")).unwrap(); + let downloaded_large = fs::read_to_string(&large_input).unwrap(); + assert_eq!(original_large, downloaded_large, "large_input.json content does not match"); +} diff --git a/crates/bench_tools/src/lib.rs b/crates/bench_tools/src/lib.rs new file mode 100644 index 00000000000..48846898270 --- /dev/null +++ b/crates/bench_tools/src/lib.rs @@ -0,0 +1,13 @@ +#[cfg(test)] +pub(crate) mod benches; +pub mod comparison; +pub mod gcs; +#[cfg(test)] +pub mod gcs_test; +pub mod runner; +#[cfg(test)] +pub mod test_utils; +pub mod types; +pub mod utils; +#[cfg(test)] +mod utils_test; diff --git a/crates/bench_tools/src/main.rs b/crates/bench_tools/src/main.rs new file mode 100644 index 00000000000..4f39445e8f7 --- /dev/null +++ b/crates/bench_tools/src/main.rs @@ -0,0 +1,144 @@ +use std::path::PathBuf; + +use bench_tools::gcs; +use bench_tools::types::benchmark_config::{ + find_benchmark_by_name, + find_benchmarks_by_package, + BENCHMARKS, +}; +use bench_tools::utils::parse_absolute_time_limits; +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(about = "Benchmark runner and comparison tool for CI.")] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Run benchmarks and output results. + Run { + /// Package name to run benchmarks for. + #[arg(short, long)] + package: String, + /// Output directory for results. + #[arg(short, long)] + out: String, + /// Optional: Local directory containing input files. If not provided, inputs will be + /// downloaded from GCS for benchmarks that require them. + #[arg(long)] + input_dir: Option, + }, + /// Run benchmarks, compare to previous run, and fail if regression exceeds limit. + RunAndCompare { + /// Package name to run benchmarks for. + #[arg(short, long)] + package: String, + /// Output directory for results. + #[arg(short, long)] + out: String, + /// Optional: Local directory containing input files. If not provided, inputs will be + /// downloaded from GCS for benchmarks that require them. + #[arg(long)] + input_dir: Option, + /// Maximum acceptable regression percentage (e.g., 5.0 for 5%). + #[arg(long)] + regression_limit: f64, + /// Set absolute time limit for a specific benchmark (can be used multiple times). + /// Format: --set-absolute-time-ns-limit + #[arg(long, value_names = ["BENCH_NAME", "LIMIT_NS"], num_args = 2, action = clap::ArgAction::Append)] + set_absolute_time_ns_limit: Vec, + }, + /// List benchmarks for a package. + List { + /// Package name to list benchmarks for. If not provided, lists all benchmarks. + #[arg(short, long)] + package: Option, + }, + /// Upload benchmark input files to GCS. + UploadInputs { + /// Benchmark name. + #[arg(long)] + benchmark: String, + /// Local directory containing input files. + #[arg(long)] + input_dir: String, + }, +} + +fn main() { + let cli = Cli::parse(); + match cli.command { + Commands::Run { package, out, input_dir } => { + let benchmarks = find_benchmarks_by_package(&package); + + if benchmarks.is_empty() { + panic!("No benchmarks found for package: {}", package); + } + + bench_tools::runner::run_benchmarks(&benchmarks, input_dir.as_deref(), &out); + } + Commands::RunAndCompare { + package, + out, + input_dir, + regression_limit, + set_absolute_time_ns_limit, + } => { + let benchmarks = find_benchmarks_by_package(&package); + + if benchmarks.is_empty() { + panic!("No benchmarks found for package: {}", package); + } + + let absolute_time_ns_limits = parse_absolute_time_limits(set_absolute_time_ns_limit); + + bench_tools::runner::run_and_compare_benchmarks( + &benchmarks, + input_dir.as_deref(), + &out, + regression_limit, + absolute_time_ns_limits, + ); + } + Commands::List { package } => match package { + Some(package_name) => { + let benchmarks = find_benchmarks_by_package(&package_name); + + if benchmarks.is_empty() { + println!("No benchmarks found for package: {}", package_name); + return; + } + + println!("Available benchmarks for package '{}':", package_name); + for bench in &benchmarks { + println!(" - {} (runs: {})", bench.name, bench.cmd_args.join(" ")); + } + } + None => { + println!("All available benchmarks:"); + for bench in BENCHMARKS { + println!( + " - {} (package: {}, runs: {})", + bench.name, + bench.package, + bench.cmd_args.join(" ") + ); + } + } + }, + Commands::UploadInputs { benchmark, input_dir } => { + // Validate benchmark exists. + if find_benchmark_by_name(&benchmark).is_none() { + panic!("Unknown benchmark: {}", benchmark); + } + + let input_path = PathBuf::from(&input_dir); + gcs::upload_inputs(&benchmark, &input_path); + + println!("Input files uploaded successfully!"); + } + } +} diff --git a/crates/bench_tools/src/runner.rs b/crates/bench_tools/src/runner.rs new file mode 100644 index 00000000000..931168a5087 --- /dev/null +++ b/crates/bench_tools/src/runner.rs @@ -0,0 +1,172 @@ +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +use crate::gcs; +use crate::types::benchmark_config::BenchmarkConfig; +use crate::utils::copy_dir_contents; + +/// Prepares inputs for a benchmark. +/// If the benchmark needs inputs and a local input directory is provided, +/// it copies the contents from the local directory to the expected input location. +/// If the benchmark needs inputs and no local input directory is provided, +/// it downloads the inputs from GCS. +fn prepare_inputs(bench: &BenchmarkConfig, input_dir: Option<&str>) { + if !bench.needs_inputs() { + return; + } + + let benchmark_input_dir = PathBuf::from(bench.input_dir.unwrap()); + + // Create the input directory if it doesn't exist. + fs::create_dir_all(&benchmark_input_dir).unwrap_or_else(|e| { + panic!("Failed to create directory {}: {}", benchmark_input_dir.display(), e) + }); + + if let Some(local_dir) = input_dir { + let local_path = PathBuf::from(local_dir); + if !local_path.exists() { + panic!("Input directory does not exist: {}", local_dir); + } + + // Copy local directory contents to the benchmark input directory. + copy_dir_contents(&local_path, &benchmark_input_dir); + + println!("Copied inputs from {} to {}", local_dir, benchmark_input_dir.display()); + } else { + gcs::download_inputs(bench.name, &benchmark_input_dir); + if !benchmark_input_dir.exists() { + panic!( + "Failed to download inputs for {}: {}", + bench.name, + benchmark_input_dir.display() + ); + } + } +} + +/// Runs a single benchmark and panic if it fails. +fn run_single_benchmark(bench: &BenchmarkConfig) { + println!("Running: {}", bench.name); + + let status = std::process::Command::new("cargo") + .args(bench.cmd_args) + .status() + .unwrap_or_else(|e| panic!("Failed to execute {}: {}", bench.name, e)); + + if !status.success() { + panic!("\nBenchmark {} failed with exit code: {:?}", bench.name, status.code()); + } +} + +/// Collects benchmark results from criterion output and saves them to the output directory. +fn save_benchmark_results(bench: &BenchmarkConfig, output_dir: &str) { + let criterion_base = PathBuf::from("target/criterion"); + + // Get the list of criterion benchmark names to save. + // If None, use the benchmark config name. + let benchmark_names: Vec<&str> = match bench.criterion_benchmark_names { + Some(names) => names.to_vec(), + None => vec![bench.name], + }; + + // Save results for each criterion benchmark name. + for bench_name in benchmark_names { + let bench_path = criterion_base.join(bench_name); + let estimates_path = bench_path.join("new/estimates.json"); + + if let Ok(data) = fs::read_to_string(&estimates_path) { + if let Ok(json) = serde_json::from_str::(&data) { + if let Ok(pretty) = serde_json::to_string_pretty(&json) { + let dest = + PathBuf::from(output_dir).join(format!("{}_estimates.json", bench_name)); + if fs::write(&dest, pretty).is_ok() { + println!("Saved results: {}", dest.display()); + } + } + } + } + } +} + +/// Runs benchmarks for a given package, handling input downloads if needed. +pub fn run_benchmarks(benchmarks: &[&BenchmarkConfig], input_dir: Option<&str>, output_dir: &str) { + // Prepare inputs. + for bench in benchmarks { + prepare_inputs(bench, input_dir); + } + + // Create output directory. + fs::create_dir_all(output_dir).unwrap_or_else(|e| panic!("Failed to create output dir: {}", e)); + + // Run benchmarks. + for bench in benchmarks { + run_single_benchmark(bench); + save_benchmark_results(bench, output_dir); + } + + println!("\n✓ All benchmarks completed! Results saved to: {}", output_dir); +} + +/// Runs benchmarks and compares them against previous results, failing if regression exceeds limit. +pub fn run_and_compare_benchmarks( + benchmarks: &[&BenchmarkConfig], + input_dir: Option<&str>, + output_dir: &str, + regression_limit: f64, + absolute_time_ns_limits: HashMap, +) { + // Run benchmarks first. + run_benchmarks(benchmarks, input_dir, output_dir); + + // Collect all criterion benchmark names from configs. + let mut bench_names = Vec::new(); + for bench in benchmarks { + bench_names.extend(bench.criterion_benchmark_names.unwrap_or(&[bench.name])); + } + + print!("\n📊 Checking for performance regressions (limit: {}%", regression_limit); + if !absolute_time_ns_limits.is_empty() { + print!(", {} benchmark(s) with absolute time limits", absolute_time_ns_limits.len()); + } + let regression_result = crate::comparison::check_regressions( + &bench_names, + regression_limit, + &absolute_time_ns_limits, + ); + + match regression_result { + Ok(_) => { + println!("\n✅ All benchmarks passed regression check!"); + } + Err((error_msg, results)) => { + // Some benchmarks exceeded the limit - print detailed results. + println!("\nBenchmark Results:"); + for result in results { + if result.exceeds_regression_limit { + println!( + " ❌ {}: {:+.2}% (EXCEEDS {:.1}% limit)", + result.name, result.change_percentage, regression_limit + ); + } + + if result.exceeds_absolute_limit { + if let Some(&limit) = absolute_time_ns_limits.get(&result.name) { + println!( + " ❌ {}: {:.2}ns (EXCEEDS {:.0}ns limit)", + result.name, result.absolute_time_ns, limit + ); + } + } + + if !result.exceeds_regression_limit && !result.exceeds_absolute_limit { + println!( + " ✓ {}: {:+.2}% | {:.2}ns", + result.name, result.change_percentage, result.absolute_time_ns + ); + } + } + panic!("\n{}", error_msg); + } + } +} diff --git a/crates/bench_tools/src/test_utils.rs b/crates/bench_tools/src/test_utils.rs new file mode 100644 index 00000000000..bd42f94545b --- /dev/null +++ b/crates/bench_tools/src/test_utils.rs @@ -0,0 +1,11 @@ +use std::path::PathBuf; + +use rstest::fixture; + +/// Returns the bench_tools crate directory. +#[fixture] +pub fn bench_tools_crate_dir() -> PathBuf { + std::env::var("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::current_dir().unwrap()) +} diff --git a/crates/bench_tools/src/types.rs b/crates/bench_tools/src/types.rs new file mode 100644 index 00000000000..65cbc4b5a34 --- /dev/null +++ b/crates/bench_tools/src/types.rs @@ -0,0 +1,4 @@ +pub mod benchmark_config; +pub mod estimates; +#[cfg(test)] +mod estimates_test; diff --git a/crates/bench_tools/src/types/benchmark_config.rs b/crates/bench_tools/src/types/benchmark_config.rs new file mode 100644 index 00000000000..7525ad10599 --- /dev/null +++ b/crates/bench_tools/src/types/benchmark_config.rs @@ -0,0 +1,100 @@ +/// Configuration for a single benchmark. +#[derive(Debug, Clone)] +pub struct BenchmarkConfig { + pub name: &'static str, + pub package: &'static str, + pub cmd_args: &'static [&'static str], + /// Optional input directory path relative to workspace root. If set, inputs will be + /// downloaded from GCS before running the benchmark. + pub input_dir: Option<&'static str>, + /// Optional list of Criterion benchmark names that this benchmark suite produces. + /// If None, assumes a single benchmark with the same name as the config. + /// Used for regression checking to know which criterion directories to check. + pub criterion_benchmark_names: Option<&'static [&'static str]>, +} + +impl BenchmarkConfig { + /// Get the full cargo bench command as owned strings. + pub fn cmd_args_owned(&self) -> Vec { + self.cmd_args.iter().map(|s| s.to_string()).collect() + } + + /// Check if this benchmark requires input files. + pub fn needs_inputs(&self) -> bool { + self.input_dir.is_some() + } +} + +/// All available benchmarks defined as a const array. +pub const BENCHMARKS: &[BenchmarkConfig] = &[ + BenchmarkConfig { + name: "full_committer_flow", + package: "starknet_committer_and_os_cli", + cmd_args: &["bench", "-p", "starknet_committer_and_os_cli", "full_committer_flow"], + input_dir: Some("crates/starknet_committer_and_os_cli/test_inputs"), + criterion_benchmark_names: None, // Single benchmark with same name. + }, + BenchmarkConfig { + name: "single_tree_flow", + package: "starknet_committer_and_os_cli", + cmd_args: &["bench", "-p", "starknet_committer_and_os_cli", "tree_computation_flow"], + input_dir: Some("crates/starknet_committer_and_os_cli/test_inputs"), + criterion_benchmark_names: Some(&["tree_computation_flow"]), + }, + BenchmarkConfig { + name: "gateway_apply_block", + package: "apollo_gateway", + cmd_args: &["bench", "-p", "apollo_gateway", "apply_block"], + input_dir: None, + criterion_benchmark_names: None, // Single benchmark with same name. + }, + BenchmarkConfig { + name: "dummy_benchmark", + package: "bench_tools", + cmd_args: &["bench", "-p", "bench_tools", "--bench", "dummy_bench"], + input_dir: Some("crates/bench_tools/data/dummy_bench_input"), + criterion_benchmark_names: Some(&[ + "dummy_sum_100", + "dummy_sum_1000", + "dummy_process_small_input", + "dummy_process_large_input", + ]), + }, + BenchmarkConfig { + name: "transfers_sequential_benchmark_cairo_native", + package: "blockifier", + cmd_args: &[ + "bench", + "--bench", + "blockifier", + "transfers_sequential", + "--features", + "testing,cairo_native", + ], + input_dir: None, + criterion_benchmark_names: None, // Single benchmark with same name. + }, + BenchmarkConfig { + name: "transfers_sequential_benchmark_vm", + package: "blockifier", + cmd_args: &[ + "bench", + "--bench", + "blockifier", + "transfers_sequential", + "--features", + "testing", + ], + input_dir: None, + criterion_benchmark_names: None, // Single benchmark with same name. + }, +]; + +/// Helper functions for working with benchmarks. +pub fn find_benchmark_by_name(name: &str) -> Option<&'static BenchmarkConfig> { + BENCHMARKS.iter().find(|b| b.name == name) +} + +pub fn find_benchmarks_by_package(package: &str) -> Vec<&'static BenchmarkConfig> { + BENCHMARKS.iter().filter(|b| b.package == package).collect() +} diff --git a/crates/bench_tools/src/types/estimates.rs b/crates/bench_tools/src/types/estimates.rs new file mode 100644 index 00000000000..62f7ba01530 --- /dev/null +++ b/crates/bench_tools/src/types/estimates.rs @@ -0,0 +1,35 @@ +use serde::Deserialize; + +/// Criterion benchmark estimates. +#[derive(Debug, Deserialize, Default)] +#[allow(dead_code)] +pub struct Estimates { + pub mean: Stat, + pub median: Stat, + #[serde(default)] + pub std_dev: Option, + #[serde(default)] + pub median_abs_dev: Option, + #[serde(default)] + pub slope: Option, +} + +/// Statistical estimate with confidence interval. +#[derive(Debug, Deserialize, Default)] +#[allow(dead_code)] +pub struct Stat { + pub point_estimate: f64, + #[serde(default)] + pub standard_error: f64, + pub confidence_interval: ConfidenceInterval, +} + +/// Confidence interval bounds. +#[derive(Debug, Deserialize, Default)] +#[allow(dead_code)] +pub struct ConfidenceInterval { + #[serde(default)] + pub confidence_level: f64, + pub lower_bound: f64, + pub upper_bound: f64, +} diff --git a/crates/bench_tools/src/types/estimates_test.rs b/crates/bench_tools/src/types/estimates_test.rs new file mode 100644 index 00000000000..a327330e9c9 --- /dev/null +++ b/crates/bench_tools/src/types/estimates_test.rs @@ -0,0 +1,81 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use apollo_infra_utils::path::project_path; +use rstest::{fixture, rstest}; + +use crate::test_utils::bench_tools_crate_dir; +use crate::types::estimates::Estimates; + +/// Returns the directory where dummy benchmark estimate results are stored. +#[fixture] +fn dummy_bench_results_dir(bench_tools_crate_dir: PathBuf) -> PathBuf { + bench_tools_crate_dir.join("data/dummy_benches_result") +} + +/// Returns the workspace root directory. +#[fixture] +fn workspace_root() -> PathBuf { + project_path().expect("Failed to get project path") +} + +/// Returns the list of dummy benchmark names. +#[fixture] +fn dummy_bench_names() -> &'static [&'static str] { + &["dummy_sum_100", "dummy_sum_1000"] +} + +/// Helper function to deserialize dummy bench estimates JSON files in a directory. +fn assert_deserialize_dummy_bench_estimates(results_dir: &Path, bench_names: &[&str]) { + for bench_name in bench_names { + let path = results_dir.join(format!("{}_estimates.json", bench_name)); + let data = fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", path.display(), e)); + + let _est: Estimates = serde_json::from_str(&data).unwrap_or_else(|e| { + panic!("Failed to deserialize {}: {}\nContent: {}", path.display(), e, data) + }); + } +} + +#[rstest] +#[ignore] +/// Run dummy benchmark and deserialize the results. +fn run_dummy_bench_and_deserialize_estimates( + workspace_root: PathBuf, + dummy_bench_results_dir: PathBuf, + dummy_bench_names: &[&str], +) { + // 1) Run dummy benchmark. + let status = Command::new("cargo") + .args(["bench", "-p", "bench_tools", "--bench", "dummy_bench"]) + .status() + .expect("Failed to spawn `cargo bench`"); + assert!(status.success(), "`cargo bench` did not exit successfully"); + + // 2) Collect and save dummy_bench estimates.json files + fs::create_dir_all(&dummy_bench_results_dir).expect("Failed to create results directory"); + + for bench_name in dummy_bench_names { + let source_path = + workspace_root.join("target/criterion").join(bench_name).join("new/estimates.json"); + let dest_path = dummy_bench_results_dir.join(format!("{}_estimates.json", bench_name)); + + // Read, parse, and write the result to the results directory. + let data = fs::read_to_string(&source_path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", source_path.display(), e)); + let json: serde_json::Value = serde_json::from_str(&data).expect("Failed to parse JSON"); + let pretty_json = serde_json::to_string_pretty(&json).expect("Failed to serialize JSON"); + fs::write(&dest_path, pretty_json).expect("Failed to write benchmark result"); + } + + // 3) Deserialize and validate the saved results + assert_deserialize_dummy_bench_estimates(&dummy_bench_results_dir, dummy_bench_names); +} + +#[rstest] +/// Test that Estimates can be deserialized from the saved results. +fn deserialize_dummy_bench_estimates(dummy_bench_results_dir: PathBuf, dummy_bench_names: &[&str]) { + assert_deserialize_dummy_bench_estimates(&dummy_bench_results_dir, dummy_bench_names); +} diff --git a/crates/bench_tools/src/utils.rs b/crates/bench_tools/src/utils.rs new file mode 100644 index 00000000000..31e5236f5d2 --- /dev/null +++ b/crates/bench_tools/src/utils.rs @@ -0,0 +1,87 @@ +use std::collections::HashMap; +use std::fs; +use std::path::Path; + +/// Recursively copies the contents of a directory from `src` to `dst`. +/// +/// Creates the destination directory if it doesn't exist. If a file or directory +/// with the same name already exists in the destination, it will be overwritten. +/// If `src` and `dst` are the same path, this function returns early without doing anything. +/// +/// # Panics +/// Panics if the source or destination paths are files. +/// Panics if any I/O operation fails, including: +/// - Creating the destination directory +/// - Reading the source directory +/// - Copying files +/// - Accessing file metadata +pub(crate) fn copy_dir_contents(src: &Path, dst: &Path) { + // Ensure destination exists. + fs::create_dir_all(dst) + .unwrap_or_else(|e| panic!("Failed to create directory {}: {}", dst.display(), e)); + + // Verify that source and destination are directories. + if !src.is_dir() { + panic!("Source path is not a directory: {}", src.display()); + } + if !dst.is_dir() { + panic!("Destination path is not a directory: {}", dst.display()); + } + + // No-op if source and destination are the same. + // Canonicalize paths to handle relative vs absolute paths and symlinks. + let src_canonical = src + .canonicalize() + .unwrap_or_else(|e| panic!("Failed to canonicalize source path {}: {}", src.display(), e)); + let dst_canonical = dst.canonicalize().unwrap_or_else(|e| { + panic!("Failed to canonicalize destination path {}: {}", dst.display(), e) + }); + + if src_canonical == dst_canonical { + return; + } + + let entries = fs::read_dir(src) + .unwrap_or_else(|e| panic!("Failed to read directory {}: {}", src.display(), e)); + + for entry in entries { + let entry = entry.unwrap_or_else(|e| panic!("Failed to read directory entry: {}", e)); + let from = entry.path(); + let to = dst.join(entry.file_name()); + + let file_type = entry + .file_type() + .unwrap_or_else(|e| panic!("Failed to get file type for {}: {}", from.display(), e)); + + if file_type.is_dir() { + copy_dir_contents(&from, &to); + } else { + fs::copy(&from, &to).unwrap_or_else(|e| { + panic!("Failed to copy {} to {}: {}", from.display(), to.display(), e) + }); + } + } +} + +/// Parses a flat `Vec` of benchmark names and limits into a HashMap. +/// The input vector should contain pairs: [bench_name1, limit1, bench_name2, limit2, ...]. +/// +/// # Panics +/// Panics if any limit value cannot be parsed as f64. +/// Panics if the input vector has an odd number of elements. +pub fn parse_absolute_time_limits(args: Vec) -> HashMap { + assert!( + args.len() % 2 == 0, + "Invalid number of absolute time limits arguments: expected even number, got {}", + args.len() + ); + let mut limits = HashMap::new(); + for chunk in args.chunks(2) { + let bench_name = chunk[0].clone(); + let limit = chunk[1].parse::().unwrap_or_else(|_| { + panic!("Invalid limit value for benchmark '{}': '{}'", bench_name, chunk[1]) + }); + limits.insert(bench_name, limit); + } + limits +} diff --git a/crates/bench_tools/src/utils_test.rs b/crates/bench_tools/src/utils_test.rs new file mode 100644 index 00000000000..a2dfb9e32dc --- /dev/null +++ b/crates/bench_tools/src/utils_test.rs @@ -0,0 +1,143 @@ +use std::collections::HashMap; +use std::fs; +use std::path::Path; + +use rstest::rstest; +use tempfile::TempDir; + +use crate::utils::{copy_dir_contents, parse_absolute_time_limits}; + +/// Helper function to create a test directory structure in a temporary directory. +/// Returns the TempDir to keep it alive for the duration of the test. +fn create_test_dir_structure() -> TempDir { + let temp_dir = TempDir::new().unwrap(); + let base = temp_dir.path(); + + // Create files + fs::write(base.join("file1.txt"), "content1").unwrap(); + fs::write(base.join("file2.txt"), "content2").unwrap(); + + // Create subdirectory with files + fs::create_dir(base.join("subdir")).unwrap(); + fs::write(base.join("subdir/file3.txt"), "content3").unwrap(); + fs::write(base.join("subdir/file4.txt"), "content4").unwrap(); + + // Create nested subdirectory + fs::create_dir(base.join("subdir/nested")).unwrap(); + fs::write(base.join("subdir/nested/file5.txt"), "content5").unwrap(); + + temp_dir +} + +/// Verifies the test directory structure created by create_test_dir_structure. +fn verify_test_dir_structure(dir: &Path) { + assert!(dir.exists()); + assert!(dir.is_dir()); + + // Verify top-level files + assert!(dir.join("file1.txt").exists()); + assert!(dir.join("file2.txt").exists()); + assert_eq!(fs::read_to_string(dir.join("file1.txt")).unwrap(), "content1"); + assert_eq!(fs::read_to_string(dir.join("file2.txt")).unwrap(), "content2"); + + // Verify subdir and its files + assert!(dir.join("subdir").is_dir()); + assert!(dir.join("subdir/file3.txt").exists()); + assert!(dir.join("subdir/file4.txt").exists()); + assert_eq!(fs::read_to_string(dir.join("subdir/file3.txt")).unwrap(), "content3"); + assert_eq!(fs::read_to_string(dir.join("subdir/file4.txt")).unwrap(), "content4"); + + // Verify nested subdir and its file + assert!(dir.join("subdir/nested").is_dir()); + assert!(dir.join("subdir/nested/file5.txt").exists()); + assert_eq!(fs::read_to_string(dir.join("subdir/nested/file5.txt")).unwrap(), "content5"); +} + +#[rstest] +fn test_copy_basic_structure() { + let temp_src = create_test_dir_structure(); + let temp_dst = TempDir::new().unwrap(); + + copy_dir_contents(temp_src.path(), temp_dst.path()); + + verify_test_dir_structure(temp_dst.path()); +} + +#[rstest] +fn test_copy_to_nonexistent_destination() { + let temp_src = create_test_dir_structure(); + let temp_parent = TempDir::new().unwrap(); + let dst = temp_parent.path().join("new_dir"); + + assert!(!dst.exists()); + copy_dir_contents(temp_src.path(), &dst); + + verify_test_dir_structure(&dst); +} + +#[rstest] +fn test_copy_overwrites_existing_files() { + let temp_src = TempDir::new().unwrap(); + fs::write(temp_src.path().join("file.txt"), "new content").unwrap(); + + let temp_dst = TempDir::new().unwrap(); + fs::write(temp_dst.path().join("file.txt"), "old content").unwrap(); + + copy_dir_contents(temp_src.path(), temp_dst.path()); + + assert_eq!(fs::read_to_string(temp_dst.path().join("file.txt")).unwrap(), "new content"); +} + +#[rstest] +/// Tests that copying a directory to the same path does nothing. +fn test_copy_same_path() { + let temp = create_test_dir_structure(); + + copy_dir_contents(temp.path(), temp.path()); + + verify_test_dir_structure(temp.path()); +} + +#[rstest] +#[should_panic(expected = "Source path is not a directory: /nonexistent")] +fn test_copy_nonexistent_source_panics() { + let temp_dst = TempDir::new().unwrap(); + copy_dir_contents(Path::new("/nonexistent"), temp_dst.path()); +} + +#[rstest] +#[case::basic( + vec!["bench1".to_string(), "1.5".to_string()], + HashMap::from([("bench1".to_string(), 1.5)]) +)] +#[case::empty(vec![], HashMap::new())] +#[case::multiple_pairs( + vec![ + "bench1".to_string(), + "1.5".to_string(), + "bench2".to_string(), + "2.7".to_string(), + "bench3".to_string(), + "3.0".to_string(), + ], + HashMap::from([ + ("bench1".to_string(), 1.5), + ("bench2".to_string(), 2.7), + ("bench3".to_string(), 3.0), + ]) +)] +fn test_parse_absolute_time_limits( + #[case] args: Vec, + #[case] expected: HashMap, +) { + let limits = parse_absolute_time_limits(args); + assert_eq!(limits, expected); +} + +#[rstest] +#[case::invalid_limit(vec!["bench1".to_string(), "not_a_number".to_string()])] +#[case::odd_number(vec!["bench1".to_string(), "1.5".to_string(), "bench2".to_string()])] +#[should_panic] +fn test_parse_absolute_time_limits_panics(#[case] args: Vec) { + parse_absolute_time_limits(args); +} diff --git a/crates/blockifier/benches/main.rs b/crates/blockifier/benches/main.rs index cc7c3be5ec3..d89edbe6e84 100644 --- a/crates/blockifier/benches/main.rs +++ b/crates/blockifier/benches/main.rs @@ -10,37 +10,252 @@ //! For Cairo Native compilation run the benchmarks using: //! `cargo bench --bench blockifier --features "cairo_native"`. +use std::sync::Arc; + use apollo_infra_utils::set_global_allocator; +use blockifier::blockifier::concurrent_transaction_executor::ConcurrentTransactionExecutor; +use blockifier::blockifier::config::{ + ConcurrencyConfig, + TransactionExecutorConfig, + WorkerPoolConfig, +}; +use blockifier::blockifier::transaction_executor::TransactionExecutor; +use blockifier::concurrency::worker_pool::WorkerPool; +use blockifier::context::BlockContext; +use blockifier::state::cached_state::CachedState; +use blockifier::test_utils::dict_state_reader::DictStateReader; use blockifier::test_utils::transfers_generator::{ + ExecutorWrapper, RecipientGeneratorType, TransfersGenerator, TransfersGeneratorConfig, }; -#[cfg(feature = "cairo_native")] +use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::test_utils::{create_test_init_data, TestInitData}; +use blockifier::transaction::transaction_execution::Transaction; use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1}; -use criterion::{criterion_group, criterion_main, Criterion}; +use blockifier_test_utils::calldata::create_calldata; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use starknet_api::invoke_tx_args; +use starknet_api::test_utils::invoke::executable_invoke_tx; +use starknet_api::transaction::TransactionVersion; + +/// The name of the benchmarks without the suffix. +const SEQUENTIAL_TRANSFERS_BENCHMARK_NAME: &str = "transfers_sequential_benchmark"; +const CONCURRENT_TRANSFERS_BENCHMARK_NAME: &str = "transfers_concurrent_benchmark"; +const HEAVY_TX_CONCURRENT_BENCHMARK_NAME: &str = "heavy_tx_benchmark_concurrent"; +const HEAVY_TX_SEQUENTIAL_BENCHMARK_NAME: &str = "heavy_tx_benchmark_sequential"; + +/// Suffix for benchmark names to differentiate Cairo Native vs VM. +#[cfg(feature = "cairo_native")] +const BENCHMARK_NAME_SUFFIX: &str = "_cairo_native"; +#[cfg(not(feature = "cairo_native"))] +const BENCHMARK_NAME_SUFFIX: &str = "_vm"; + +const HEAVY_TX_ENTRY_POINT: &str = "test_builtin_counts_consistency"; + +/// Cairo version to use for benchmarks, selected based on the cairo_native feature. +#[cfg(feature = "cairo_native")] +const CAIRO_VERSION: CairoVersion = CairoVersion::Cairo1(RunnableCairo1::Native); +#[cfg(not(feature = "cairo_native"))] +const CAIRO_VERSION: CairoVersion = CairoVersion::Cairo1(RunnableCairo1::Casm); // TODO(Arni): Consider how to run this benchmark both with and without setting the allocator. Maybe // hide this macro call under a feature, and run this benchmark regularly or with // `cargo bench --bench blockifier --feature=specified_allocator` set_global_allocator!(); -pub fn transfers_benchmark(c: &mut Criterion) { +/// Returns heavy transaction that calls HEAVY_TX_ENTRY_POINT, +/// and initializes the state with the test_init_data. +fn setup_heavy_tx_benchmark( + block_context: &BlockContext, +) -> (Transaction, CachedState) { + // Select Cairo version based on feature flag. + #[cfg(feature = "cairo_native")] + let cairo_version = CairoVersion::Cairo1(RunnableCairo1::Native); + #[cfg(not(feature = "cairo_native"))] + let cairo_version = CairoVersion::Cairo1(RunnableCairo1::Casm); + + let TestInitData { state, account_address, contract_address, mut nonce_manager } = + create_test_init_data(block_context.chain_info(), cairo_version); + + let entry_point_args = vec![]; + let calldata = create_calldata(contract_address, HEAVY_TX_ENTRY_POINT, &entry_point_args); + + let invoke_tx = executable_invoke_tx(invoke_tx_args! { + sender_address: account_address, + calldata, + nonce: nonce_manager.next(account_address), + version: TransactionVersion::THREE, + }); + let account_tx = AccountTransaction::new_for_sequencing(invoke_tx); + let tx = Transaction::Account(account_tx); + + (tx, state) +} + +/// Benchmarks the execution phase of the transfers flow. +/// Benchmarks the execution phase of the transfers flow (sequential). +/// The sender account is chosen round-robin. +/// The recipient account is chosen randomly. +pub fn transfers_benchmark_sequential(c: &mut Criterion) { + let transfers_generator_config = TransfersGeneratorConfig { + recipient_generator_type: RecipientGeneratorType::Random, + cairo_version: CAIRO_VERSION, + concurrency_config: ConcurrencyConfig::create_for_testing(false), + ..Default::default() + }; + let mut transfers_generator = TransfersGenerator::new(transfers_generator_config); + // Benchmark only the execution phase (run_block_of_transfers call). + // Transaction generation and state setup happen for each iteration but are not timed. + c.bench_function( + &format!("{}{}", SEQUENTIAL_TRANSFERS_BENCHMARK_NAME, BENCHMARK_NAME_SUFFIX), + |benchmark| { + benchmark.iter_batched( + || { + // Setup: prepare transactions and executor (not measured). + transfers_generator.prepare_to_run_block_of_transfers(None, None) + }, + |(txs, mut executor_wrapper)| { + // Measured: execute the transactions. + TransfersGenerator::run_block_of_transfers(&txs, &mut executor_wrapper, None) + }, + BatchSize::SmallInput, + ) + }, + ); +} + +/// Benchmarks the execution phase of the transfers flow (concurrent). +/// The sender account is chosen round-robin. +/// The recipient account is chosen randomly. +pub fn transfers_benchmark_concurrent(c: &mut Criterion) { let transfers_generator_config = TransfersGeneratorConfig { recipient_generator_type: RecipientGeneratorType::Random, #[cfg(feature = "cairo_native")] - cairo_version: CairoVersion::Cairo1(RunnableCairo1::Native), + cairo_version: CAIRO_VERSION, + concurrency_config: ConcurrencyConfig::create_for_testing(true), ..Default::default() }; let mut transfers_generator = TransfersGenerator::new(transfers_generator_config); - // Create a benchmark group called "transfers", which iterates over the accounts round-robin - // and performs transfers. - c.bench_function("transfers", |benchmark| { - benchmark.iter(|| { - transfers_generator.execute_block_of_transfers(None); - }) + + // Create worker pool before benchmarking. + let worker_pool = Arc::new(WorkerPool::start(&WorkerPoolConfig::create_for_testing())); + + // Benchmark only the execution phase (run_block_of_transfers call). + // Transaction generation and state setup happen for each iteration but are not timed. + c.bench_function( + &format!("{}{}", CONCURRENT_TRANSFERS_BENCHMARK_NAME, BENCHMARK_NAME_SUFFIX), + |benchmark| { + benchmark.iter_batched( + || { + // Setup: prepare transactions and executor (not measured). + transfers_generator + .prepare_to_run_block_of_transfers(Some(worker_pool.clone()), None) + }, + |(txs, mut executor_wrapper)| { + // Measured: execute the transactions. + TransfersGenerator::run_block_of_transfers(&txs, &mut executor_wrapper, None); + match executor_wrapper { + ExecutorWrapper::Concurrent(ref mut executor, _) => { + executor.abort_block(); + } + _ => panic!("Executor wrapper is not a concurrent executor"), + } + }, + BatchSize::SmallInput, + ) + }, + ); + + // Cleanup worker pool after all benchmark iterations complete. + Arc::try_unwrap(worker_pool).expect("More than one instance of worker pool exists").join(); +} + +/// Benchmarks the execution phase of `HEAVY_TX_ENTRY_POINT` using +/// ConcurrentTransactionExecutor. +pub fn heavy_tx_benchmark_concurrent(c: &mut Criterion) { + let block_context = BlockContext::create_for_account_testing(); + + let executor_config = TransactionExecutorConfig::create_for_testing(true); + let worker_pool = Arc::new(WorkerPool::start(&executor_config.get_worker_pool_config())); + + let bench_name = format!("{}{}", HEAVY_TX_CONCURRENT_BENCHMARK_NAME, BENCHMARK_NAME_SUFFIX); + c.bench_function(&bench_name, |benchmark| { + benchmark.iter_batched( + || { + // Setup: prepare transaction and executor (not measured). + let (tx, state) = setup_heavy_tx_benchmark(&block_context); + + let executor = ConcurrentTransactionExecutor::new_for_testing( + state, + block_context.clone(), + worker_pool.clone(), + None, + ); + + (tx, executor) + }, + |(tx, mut executor)| { + // Measured: execute the transaction. + let results = executor.add_txs_and_wait(&[tx]); + let tx_execution_info = &results[0].as_ref().unwrap().0; + assert!( + !tx_execution_info.is_reverted(), + "Transaction reverted: {:?}", + tx_execution_info.revert_error + ); + // Abort the block to allow the worker threads to continue to the next block. + executor.abort_block(); + }, + BatchSize::SmallInput, + ) + }); + + // Cleanup worker pool after all benchmark iterations complete. + Arc::try_unwrap(worker_pool).expect("More than one instance of worker pool exists").join(); +} + +/// Benchmarks the execution phase of `HEAVY_TX_ENTRY_POINT` using +/// TransactionExecutor (sequential). +pub fn heavy_tx_benchmark_sequential(c: &mut Criterion) { + let block_context = BlockContext::create_for_account_testing(); + + let bench_name = format!("{}{}", HEAVY_TX_SEQUENTIAL_BENCHMARK_NAME, BENCHMARK_NAME_SUFFIX); + c.bench_function(&bench_name, |benchmark| { + benchmark.iter_batched( + || { + // Setup: prepare transaction and executor (not measured). + let (tx, state) = setup_heavy_tx_benchmark(&block_context); + + let executor_config = TransactionExecutorConfig::create_for_testing(false); + let executor = + TransactionExecutor::new(state, block_context.clone(), executor_config); + + (tx, executor) + }, + |(tx, mut executor)| { + // Measured: execute the transaction. + let results = executor.execute_txs(&[tx], None); + let tx_execution_info = &results[0].as_ref().unwrap().0; + assert!( + !tx_execution_info.is_reverted(), + "Transaction reverted: {:?}", + tx_execution_info.revert_error + ); + }, + BatchSize::SmallInput, + ) }); } -criterion_group!(benches, transfers_benchmark); +criterion_group! { + name = benches; + config = Criterion::default().sample_size(30); + targets = transfers_benchmark_sequential, + transfers_benchmark_concurrent, + heavy_tx_benchmark_concurrent, + heavy_tx_benchmark_sequential +} + criterion_main!(benches); diff --git a/crates/blockifier/src/blockifier/transaction_executor.rs b/crates/blockifier/src/blockifier/transaction_executor.rs index ceff26c2d16..7bce3bf4c45 100644 --- a/crates/blockifier/src/blockifier/transaction_executor.rs +++ b/crates/blockifier/src/blockifier/transaction_executor.rs @@ -24,7 +24,6 @@ use crate::transaction::errors::TransactionExecutionError; use crate::transaction::objects::TransactionExecutionInfo; use crate::transaction::transaction_execution::Transaction; use crate::transaction::transactions::ExecutableTransaction; - #[cfg(test)] #[path = "transaction_executor_test.rs"] pub mod transaction_executor_test; @@ -241,6 +240,7 @@ pub(crate) fn finalize_block( block_context.block_info.block_number, bouncer.get_bouncer_weights() ); + let alias_contract_address = block_context .versioned_constants .os_constants @@ -252,10 +252,14 @@ pub(crate) fn finalize_block( let mut bouncer = bouncer; let class_hashes_to_migrate = mem::take(bouncer.get_mut_class_hashes_to_migrate()); - log::trace!( - "Class hashes to migrate (key = class_hash, value = (compiled_class_hash_v2, \ - compiled_class_hash_v1)): {class_hashes_to_migrate:#?}" - ); + #[cfg(any(test, feature = "testing"))] + if !class_hashes_to_migrate.is_empty() { + log::info!( + "Class hashes to migrate (key = class_hash, value = (compiled_class_hash_v2, \ + compiled_class_hash_v1)): {class_hashes_to_migrate:#?}" + ); + } + if !block_context.versioned_constants.enable_casm_hash_migration { assert!( class_hashes_to_migrate.is_empty(), diff --git a/crates/blockifier/src/blockifier_versioned_constants.rs b/crates/blockifier/src/blockifier_versioned_constants.rs index 8e8513257ca..a673a74fe3e 100644 --- a/crates/blockifier/src/blockifier_versioned_constants.rs +++ b/crates/blockifier/src/blockifier_versioned_constants.rs @@ -1392,7 +1392,7 @@ impl SerializeConfig for VersionedConstantsOverrides { ser_param( "max_n_events", &self.max_n_events, - "Maximum number of events that can be emitted from the transation.", + "Maximum number of events that can be emitted from the transaction.", ParamPrivacyInput::Public, ), ]) diff --git a/crates/blockifier/src/bouncer.rs b/crates/blockifier/src/bouncer.rs index c107961627d..ff6de60e6e7 100644 --- a/crates/blockifier/src/bouncer.rs +++ b/crates/blockifier/src/bouncer.rs @@ -196,7 +196,7 @@ impl Default for BouncerWeights { n_txs: 600, state_diff_size: 4000, // NOTE: Must stay in sync with orchestrator_versioned_constants' max_block_size. - sierra_gas: GasAmount(5000000000), + sierra_gas: GasAmount(6000000000), proving_gas: GasAmount(6000000000), } } @@ -322,7 +322,7 @@ impl CasmHashComputationData { /// Tracks which classes need migration from V1 to V2 compiled hashes and /// accumulates the estimated execution resources required to perform the migration. struct CasmHashMigrationData { - class_hashes_to_migrate: HashMap, + pub(crate) class_hashes_to_migrate: HashMap, resources: EstimatedExecutionResources, } @@ -859,6 +859,13 @@ pub fn get_tx_weights( executed_class_hashes, versioned_constants, )?; + // Total state changes keys are the sum of marginal state changes keys and the + // migration state changes. + let mut total_state_changes_keys = StateChangesKeys { + compiled_class_hash_keys: migration_data.class_hashes_to_migrate.keys().cloned().collect(), + ..Default::default() + }; + total_state_changes_keys.extend(state_changes_keys); let blake_opcode_gas = bouncer_config.blake_weight; @@ -903,7 +910,7 @@ pub fn get_tx_weights( l1_gas: message_starknet_l1gas, message_segment_length: message_resources.message_segment_length, n_events: tx_resources.starknet_resources.archival_data.event_summary.n_events, - state_diff_size: get_onchain_data_segment_length(&state_changes_keys.count()), + state_diff_size: get_onchain_data_segment_length(&total_state_changes_keys.count()), sierra_gas: total_sierra_gas, n_txs: 1, proving_gas: total_proving_gas, @@ -939,15 +946,21 @@ pub fn map_class_hash_to_casm_hash_computation_resources( // and number of visited leaves includes reads and writes. pub fn get_particia_update_resources(n_visited_storage_entries: usize) -> ExecutionResources { const TREE_HEIGHT_UPPER_BOUND: usize = 24; - let n_updates = n_visited_storage_entries * TREE_HEIGHT_UPPER_BOUND; - - ExecutionResources { - // TODO(Yoni, 1/5/2024): re-estimate this. - n_steps: 32 * n_updates, - // For each Patricia update there are two hash calculations. - builtin_instance_counter: HashMap::from([(BuiltinName::pedersen, 2 * n_updates)]), + // TODO(Yoni, 1/5/2024): re-estimate this. + const STEPS_IN_TREE_PER_HEIGHT: usize = 16; + const PEDERSENS_PER_HEIGHT: usize = 1; + + let resources_per_tree_access = ExecutionResources { + n_steps: TREE_HEIGHT_UPPER_BOUND * STEPS_IN_TREE_PER_HEIGHT, + builtin_instance_counter: HashMap::from([( + BuiltinName::pedersen, + TREE_HEIGHT_UPPER_BOUND * PEDERSENS_PER_HEIGHT, + )]), n_memory_holes: 0, - } + }; + + // Multiply by 2 since each storage entry is accessed in both the old and new tree. + &resources_per_tree_access * (n_visited_storage_entries * 2) } pub fn verify_tx_weights_within_max_capacity( diff --git a/crates/blockifier/src/bouncer_test.rs b/crates/blockifier/src/bouncer_test.rs index dde9bbf5dd7..042646f5507 100644 --- a/crates/blockifier/src/bouncer_test.rs +++ b/crates/blockifier/src/bouncer_test.rs @@ -410,23 +410,55 @@ fn test_bouncer_try_update_n_txs( let accumulated_weights = TxWeights { bouncer_weights, ..Default::default() }; let mut bouncer = Bouncer { accumulated_weights, bouncer_config, ..Bouncer::empty() }; + bouncer.bouncer_config.block_max_capacity.sierra_gas = GasAmount::MAX; + bouncer.bouncer_config.block_max_capacity.proving_gas = GasAmount::MAX; // Prepare first tx resources. let mut first_transactional_state = TransactionalState::create_transactional(&mut state); - let first_tx_state_changes_keys = - first_transactional_state.to_state_diff().unwrap().state_maps.keys(); + let first_tx_state_changes_keys = StateChangesKeys { + storage_keys: HashSet::from([(contract_address!(1_u128), storage_key!(1_u128))]), + modified_contracts: HashSet::from([contract_address!(1_u128)]), + ..StateChangesKeys::default() + }; + let first_tx_execution_summary = ExecutionSummary { + visited_storage_entries: HashSet::from([ + (contract_address!(1_u128), storage_key!(1_u128)), + (contract_address!(2_u128), storage_key!(2_u128)), + ]), + ..ExecutionSummary::default() + }; // Try to update the bouncer. let mut result = bouncer.try_update( &first_transactional_state, &first_tx_state_changes_keys, - &ExecutionSummary::default(), + &first_tx_execution_summary, &BuiltinCounterMap::default(), &TransactionResources::default(), &block_context.versioned_constants, ); assert_matches!(result, Ok(())); + // Regression test to cover complicated calculations such as patricia update. + expect![ + r#" + BouncerWeights { + l1_gas: 10, + message_segment_length: 10, + n_events: 10, + state_diff_size: 14, + sierra_gas: GasAmount( + 542410, + ), + n_txs: 20, + proving_gas: GasAmount( + 702922, + ), + } + "# + ] + .assert_debug_eq(&bouncer.accumulated_weights.bouncer_weights); + // Prepare second tx resources. let mut second_transactional_state = TransactionalState::create_transactional(&mut first_transactional_state); diff --git a/crates/blockifier/src/concurrency/worker_logic.rs b/crates/blockifier/src/concurrency/worker_logic.rs index 1f1b3fe2665..fd67dbe38e6 100644 --- a/crates/blockifier/src/concurrency/worker_logic.rs +++ b/crates/blockifier/src/concurrency/worker_logic.rs @@ -41,6 +41,7 @@ pub struct ExecutionTaskOutput { pub reads: StateMaps, pub state_diff: StateMaps, pub contract_classes: ContractClassMapping, + pub run_time: Duration, pub result: TransactionExecutionResult, } @@ -239,8 +240,10 @@ impl WorkerExecutor { TransactionalState::create_transactional(&mut tx_versioned_state); let concurrency_mode = true; let tx = self.tx_at(tx_index); + let execution_start = Instant::now(); let execution_result = tx.execute_raw(&mut transactional_state, &self.block_context, concurrency_mode); + let run_time = execution_start.elapsed(); // Update the versioned state and store the transaction execution output. let execution_output_inner = match execution_result { @@ -253,6 +256,7 @@ impl WorkerExecutor { reads: tx_reads_writes.initial_reads, state_diff, contract_classes, + run_time, result: execution_result, } } @@ -261,6 +265,7 @@ impl WorkerExecutor { // Failed transaction - ignore the writes. state_diff: StateMaps::default(), contract_classes: HashMap::default(), + run_time, result: execution_result, }, }; @@ -321,9 +326,10 @@ impl WorkerExecutor { let mut execution_output_refmut = self.lock_execution_output(tx_index); let execution_output = execution_output_refmut.value_mut(); let mut tx_state_changes_keys = execution_output.state_diff.keys(); + let tx = self.tx_at(tx_index); + let execution_status: &str; if let Ok(tx_execution_info) = execution_output.result.as_mut() { - let tx = self.tx_at(tx_index); let tx_context = self.block_context.to_tx_context(tx.as_ref()); // Add the deleted sequencer balance key to the storage keys. let concurrency_mode = true; @@ -364,10 +370,26 @@ impl WorkerExecutor { &mut tx_versioned_state, tx.as_ref(), ); + + execution_status = + if tx_execution_info.is_reverted() { "reverted" } else { "successfully executed" }; + // Optimization: changing the sequencer balance storage cell does not trigger // (re-)validation of the next transactions. + } else { + execution_status = "rejected"; } + let tx_hash = Transaction::tx_hash(tx.as_ref()); + let run_time = execution_output.run_time.as_millis(); + let n_reads = execution_output.reads.storage.len(); + let n_writes = execution_output.state_diff.storage.len(); + log::debug!( + "Transaction with tx_hash: {tx_hash} {execution_status}. Execution time: \ + {run_time}ms. number of storage reads: {n_reads}, number of storage writes: \ + {n_writes}." + ); + Ok(CommitResult::Success) } diff --git a/crates/blockifier/src/execution/call_info.rs b/crates/blockifier/src/execution/call_info.rs index a1ae1590289..15dfa772b8a 100644 --- a/crates/blockifier/src/execution/call_info.rs +++ b/crates/blockifier/src/execution/call_info.rs @@ -16,6 +16,7 @@ use starknet_types_core::felt::Felt; use crate::blockifier_versioned_constants::VersionedConstants; use crate::execution::contract_class::TrackedResource; use crate::execution::entry_point::CallEntryPoint; +use crate::execution::syscalls::vm_syscall_utils::SyscallUsageMap; use crate::state::cached_state::StorageEntry; use crate::utils::u64_from_usize; @@ -240,6 +241,8 @@ pub struct CallInfo { // Tracks how many times each builtin was called during execution (excluding inner calls). // Used by the bouncer to decide when to close a block. pub builtin_counters: BuiltinCounterMap, + // Tracks how many times each syscall was called during execution (excluding inner calls). + pub syscalls_usage: SyscallUsageMap, } impl CallInfo { diff --git a/crates/blockifier/src/execution/deprecated_entry_point_execution.rs b/crates/blockifier/src/execution/deprecated_entry_point_execution.rs index 50d8c560ae2..8ac6d257754 100644 --- a/crates/blockifier/src/execution/deprecated_entry_point_execution.rs +++ b/crates/blockifier/src/execution/deprecated_entry_point_execution.rs @@ -289,6 +289,7 @@ pub fn finalize_execution( ..Default::default() }, builtin_counters: vm_resources_without_inner_calls.prover_builtins(), + syscalls_usage: syscall_handler.syscalls_usage, }) } diff --git a/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs b/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs index 37a7d22f84e..98c3cca8c25 100644 --- a/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs +++ b/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs @@ -39,6 +39,7 @@ use crate::execution::deprecated_syscalls::DeprecatedSyscallSelector; use crate::execution::entry_point::{CallEntryPoint, CallType}; use crate::execution::errors::EntryPointExecutionError; use crate::execution::syscalls::hint_processor::EmitEventError; +use crate::execution::syscalls::vm_syscall_utils::{SyscallSelector, SyscallUsage}; use crate::state::state_api::StateReader; use crate::test_utils::contracts::FeatureContractData; use crate::test_utils::initial_test_state::{test_state, test_state_inner}; @@ -152,6 +153,10 @@ fn test_nested_library_call() { n_memory_holes: 0, builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 2)]), }; + let storage_entry_point_syscalls_usage = HashMap::from([ + (SyscallSelector::StorageRead, SyscallUsage::with_call_count(1)), + (SyscallSelector::StorageWrite, SyscallUsage::with_call_count(1)), + ]); let nested_storage_call_info = CallInfo { call: nested_storage_entry_point, execution: CallExecution::from_retdata(retdata![felt!(value + 1)]), @@ -162,6 +167,7 @@ fn test_nested_library_call() { ..Default::default() }, builtin_counters: HashMap::from([(BuiltinName::range_check, 2)]), + syscalls_usage: storage_entry_point_syscalls_usage.clone(), ..Default::default() }; let mut library_call_resources = @@ -178,6 +184,10 @@ fn test_nested_library_call() { resources: library_call_resources.clone(), inner_calls: vec![nested_storage_call_info], builtin_counters: HashMap::from([(BuiltinName::range_check, 19)]), + syscalls_usage: HashMap::from([( + SyscallSelector::LibraryCall, + SyscallUsage::with_call_count(1), + )]), ..Default::default() }; let storage_call_info = CallInfo { @@ -190,6 +200,7 @@ fn test_nested_library_call() { ..Default::default() }, builtin_counters: HashMap::from([(BuiltinName::range_check, 2)]), + syscalls_usage: storage_entry_point_syscalls_usage.clone(), ..Default::default() }; @@ -208,6 +219,10 @@ fn test_nested_library_call() { resources: main_call_resources, inner_calls: vec![library_call_info, storage_call_info], builtin_counters: HashMap::from([(BuiltinName::range_check, 37)]), + syscalls_usage: HashMap::from([( + SyscallSelector::LibraryCall, + SyscallUsage::with_call_count(2), + )]), ..Default::default() }; @@ -309,6 +324,10 @@ fn test_call_contract() { ..Default::default() }, builtin_counters: HashMap::from([(BuiltinName::range_check, 2)]), + syscalls_usage: HashMap::from([ + (SyscallSelector::StorageWrite, SyscallUsage::with_call_count(1)), + (SyscallSelector::StorageRead, SyscallUsage::with_call_count(1)), + ]), ..Default::default() }; let expected_call_info = CallInfo { @@ -327,6 +346,10 @@ fn test_call_contract() { builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 3)]), }, builtin_counters: HashMap::from([(BuiltinName::range_check, 19)]), + syscalls_usage: HashMap::from([( + SyscallSelector::CallContract, + SyscallUsage::with_call_count(1), + )]), ..Default::default() }; diff --git a/crates/blockifier/src/execution/entry_point_execution.rs b/crates/blockifier/src/execution/entry_point_execution.rs index 29951fc6b04..6fe55b3f4a4 100644 --- a/crates/blockifier/src/execution/entry_point_execution.rs +++ b/crates/blockifier/src/execution/entry_point_execution.rs @@ -487,6 +487,7 @@ pub fn finalize_execution( resources: vm_resources, storage_access_tracker: syscall_handler_base.storage_access_tracker, builtin_counters: vm_resources_without_inner_calls.prover_builtins(), + syscalls_usage: syscall_handler_base.syscalls_usage, }) } diff --git a/crates/blockifier/src/execution/native/entry_point_execution.rs b/crates/blockifier/src/execution/native/entry_point_execution.rs index d005b3d45d7..5d3a597e457 100644 --- a/crates/blockifier/src/execution/native/entry_point_execution.rs +++ b/crates/blockifier/src/execution/native/entry_point_execution.rs @@ -114,6 +114,7 @@ fn create_callinfo( storage_access_tracker: syscall_handler.base.storage_access_tracker, tracked_resource: TrackedResource::SierraGas, builtin_counters, + syscalls_usage: syscall_handler.base.syscalls_usage, }) } diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/deploy.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/deploy.rs index 2e85210a58b..d86a8e13799 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/deploy.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/deploy.rs @@ -280,7 +280,7 @@ fn calldata_length(cairo_version: CairoVersion) { .versioned_constants .get_additional_os_syscall_resources(&HashMap::from([( SyscallSelector::Deploy, - (SyscallUsage::new(1, 0)), + SyscallUsage::with_call_count(1), )])) .builtin_instance_counter .get(&BuiltinName::pedersen) diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs index e290a28272a..eb0a5a975ec 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs @@ -13,6 +13,7 @@ use crate::blockifier_versioned_constants::VersionedConstants; use crate::context::ChainInfo; use crate::execution::call_info::{CallExecution, CallInfo, Retdata, StorageAccessTracker}; use crate::execution::entry_point::{CallEntryPoint, CallType}; +use crate::execution::syscalls::vm_syscall_utils::{SyscallSelector, SyscallUsage}; use crate::retdata; use crate::test_utils::contracts::FeatureContractTrait; use crate::test_utils::initial_test_state::test_state; @@ -208,6 +209,10 @@ fn test_nested_library_call(runnable_version: RunnableCairo1) { ..Default::default() }, builtin_counters: HashMap::from([(BuiltinName::range_check, 7)]), + syscalls_usage: HashMap::from([ + (SyscallSelector::StorageRead, SyscallUsage::with_call_count(1)), + (SyscallSelector::StorageWrite, SyscallUsage::with_call_count(1)), + ]), ..Default::default() }; @@ -225,6 +230,10 @@ fn test_nested_library_call(runnable_version: RunnableCairo1) { inner_calls: vec![nested_storage_call_info], tracked_resource, builtin_counters: HashMap::from([(BuiltinName::range_check, 26)]), + syscalls_usage: HashMap::from([( + SyscallSelector::LibraryCall, + SyscallUsage::with_call_count(1), + )]), ..Default::default() }; @@ -246,6 +255,10 @@ fn test_nested_library_call(runnable_version: RunnableCairo1) { }, tracked_resource, builtin_counters: HashMap::from([(BuiltinName::range_check, 7)]), + syscalls_usage: HashMap::from([ + (SyscallSelector::StorageRead, SyscallUsage::with_call_count(1)), + (SyscallSelector::StorageWrite, SyscallUsage::with_call_count(1)), + ]), ..Default::default() }; @@ -263,6 +276,10 @@ fn test_nested_library_call(runnable_version: RunnableCairo1) { inner_calls: vec![library_call_info, storage_call_info], tracked_resource, builtin_counters: HashMap::from([(BuiltinName::range_check, 41)]), + syscalls_usage: HashMap::from([( + SyscallSelector::LibraryCall, + SyscallUsage::with_call_count(2), + )]), ..Default::default() }; diff --git a/crates/blockifier/src/execution/syscalls/vm_syscall_utils.rs b/crates/blockifier/src/execution/syscalls/vm_syscall_utils.rs index 02c3d031736..de32cbb0e65 100644 --- a/crates/blockifier/src/execution/syscalls/vm_syscall_utils.rs +++ b/crates/blockifier/src/execution/syscalls/vm_syscall_utils.rs @@ -48,7 +48,8 @@ pub type SyscallSelector = DeprecatedSyscallSelector; pub type SyscallUsageMap = HashMap; -#[derive(Clone, Debug, Default, Serialize)] +#[cfg_attr(feature = "transaction_serde", derive(serde::Deserialize))] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct SyscallUsage { pub call_count: usize, pub linear_factor: usize, @@ -58,7 +59,9 @@ impl SyscallUsage { pub fn new(call_count: usize, linear_factor: usize) -> Self { SyscallUsage { call_count, linear_factor } } - + pub fn with_call_count(call_count: usize) -> Self { + SyscallUsage::new(call_count, 0) + } pub fn increment_call_count(&mut self) { self.call_count += 1; } diff --git a/crates/blockifier/src/state/cached_state.rs b/crates/blockifier/src/state/cached_state.rs index cf19890c145..4baee6a22e8 100644 --- a/crates/blockifier/src/state/cached_state.rs +++ b/crates/blockifier/src/state/cached_state.rs @@ -637,13 +637,13 @@ impl From for CommitmentStateDiff { #[cfg_attr(any(feature = "testing", test), derive(Clone))] #[derive(Debug, Default, Eq, PartialEq)] pub struct StateChangesKeys { - nonce_keys: HashSet, - class_hash_keys: HashSet, - storage_keys: HashSet, - compiled_class_hash_keys: HashSet, + pub(crate) nonce_keys: HashSet, + pub(crate) class_hash_keys: HashSet, + pub(crate) storage_keys: HashSet, + pub(crate) compiled_class_hash_keys: HashSet, // Note: this field may not be consistent with the above keys; specifically, it may be // strictlly contained in them. For example, as a result of a `difference` operation. - modified_contracts: HashSet, + pub(crate) modified_contracts: HashSet, } impl StateChangesKeys { diff --git a/crates/blockifier/src/test_utils.rs b/crates/blockifier/src/test_utils.rs index 6f9e89b0e22..e29655dc35b 100644 --- a/crates/blockifier/src/test_utils.rs +++ b/crates/blockifier/src/test_utils.rs @@ -316,7 +316,7 @@ macro_rules! check_tx_execution_error_for_invalid_scenario { pub fn get_const_syscall_resources(syscall_selector: SyscallSelector) -> ExecutionResources { let versioned_constants = VersionedConstants::create_for_testing(); let syscalls_usage: SyscallUsageMap = - HashMap::from([(syscall_selector, SyscallUsage::new(1, 0))]); + HashMap::from([(syscall_selector, SyscallUsage::with_call_count(1))]); versioned_constants.get_additional_os_syscall_resources(&syscalls_usage) } diff --git a/crates/blockifier/src/test_utils/l1_handler.rs b/crates/blockifier/src/test_utils/l1_handler.rs index 3eb417cdc52..79d3b8316b0 100644 --- a/crates/blockifier/src/test_utils/l1_handler.rs +++ b/crates/blockifier/src/test_utils/l1_handler.rs @@ -1,11 +1,17 @@ +use std::sync::LazyLock; + use starknet_api::abi::abi_utils::selector_from_name; use starknet_api::calldata; -use starknet_api::core::ContractAddress; +use starknet_api::core::{ContractAddress, EntryPointSelector}; use starknet_api::executable_transaction::L1HandlerTransaction; use starknet_api::test_utils::l1_handler::{executable_l1_handler_tx, L1HandlerTxArgs}; use starknet_api::transaction::fields::Fee; use starknet_types_core::felt::Felt; +// This selector is a property of 'FeatureContract::TestContract'. +pub static L1_HANDLER_SET_VALUE_ENTRY_POINT_SELECTOR: LazyLock = + LazyLock::new(|| selector_from_name("l1_handler_set_value")); + pub fn l1handler_tx(l1_fee: Fee, contract_address: ContractAddress) -> L1HandlerTransaction { let calldata = calldata![ Felt::from(0x123), // from_address. @@ -15,7 +21,7 @@ pub fn l1handler_tx(l1_fee: Fee, contract_address: ContractAddress) -> L1Handler executable_l1_handler_tx(L1HandlerTxArgs { contract_address, - entry_point_selector: selector_from_name("l1_handler_set_value"), + entry_point_selector: *L1_HANDLER_SET_VALUE_ENTRY_POINT_SELECTOR, calldata, paid_fee_on_l1: l1_fee, ..Default::default() diff --git a/crates/blockifier/src/test_utils/transfers_generator.rs b/crates/blockifier/src/test_utils/transfers_generator.rs index 14b3c8c7191..a6e8e709a86 100644 --- a/crates/blockifier/src/test_utils/transfers_generator.rs +++ b/crates/blockifier/src/test_utils/transfers_generator.rs @@ -22,12 +22,12 @@ use crate::blockifier::config::{ConcurrencyConfig, TransactionExecutorConfig}; use crate::blockifier::transaction_executor::{ BlockExecutionSummary, TransactionExecutor, - TransactionExecutorError, DEFAULT_STACK_SIZE, }; use crate::concurrency::worker_pool::WorkerPool; use crate::context::{BlockContext, ChainInfo}; -use crate::state::cached_state::StateMaps; +use crate::state::cached_state::CachedState; +use crate::test_utils::dict_state_reader::DictStateReader; use crate::test_utils::initial_test_state::test_state; use crate::test_utils::{RunnableCairo1, BALANCE}; use crate::transaction::account_transaction::AccountTransaction; @@ -87,6 +87,16 @@ pub struct TransfersGenerator { config: TransfersGeneratorConfig, } +/// Enum to wrap both executor types for unified handling. +#[allow(clippy::large_enum_variant)] +pub enum ExecutorWrapper { + Concurrent( + ConcurrentTransactionExecutor, + Arc>>, + ), + Sequential(TransactionExecutor), +} + impl TransfersGenerator { pub fn new(config: TransfersGeneratorConfig) -> Self { let account_contract = FeatureContract::AccountWithoutValidations(config.cairo_version); @@ -161,15 +171,29 @@ impl TransfersGenerator { } } - #[allow(clippy::type_complexity)] - fn _run_txs( - &self, - txs: Vec, - execution_deadline: Option, - ) -> ( - Vec>, - BlockExecutionSummary, - ) { + /// Prepares transactions and executor for running a block. + /// Returns the transactions and the executor wrapper. + pub fn prepare_to_run_block_of_transfers( + &mut self, + worker_pool: Option>>>, + timeout: Option, + ) -> (Vec, ExecutorWrapper) { + // Reset nonce manager since we create a fresh state. + self.nonce_manager = NonceManager::default(); + + // Generate transactions + let mut txs: Vec = Vec::with_capacity(self.config.n_txs); + for _ in 0..self.config.n_txs { + let sender_address = self.account_addresses[self.sender_index]; + let recipient_address = self.get_next_recipient(); + self.sender_index = (self.sender_index + 1) % self.account_addresses.len(); + + let tx = self.generate_transfer(sender_address, recipient_address); + let account_tx = AccountTransaction::new_for_sequencing(tx); + txs.push(Transaction::Account(account_tx)); + } + + let execution_deadline = timeout.map(|timeout_duration| Instant::now() + timeout_duration); let state = test_state( &self.chain_info, self.config.balance, @@ -180,32 +204,82 @@ impl TransfersGenerator { stack_size: self.config.stack_size, }; - if executor_config.concurrency_config.enabled { - let worker_pool = - Arc::new(WorkerPool::start(&executor_config.get_worker_pool_config())); + let executor_wrapper = if executor_config.concurrency_config.enabled { + let worker_pool = worker_pool.unwrap_or_else(|| { + Arc::new(WorkerPool::start(&executor_config.get_worker_pool_config())) + }); - let mut executor = ConcurrentTransactionExecutor::new_for_testing( + let executor = ConcurrentTransactionExecutor::new_for_testing( state, self.block_context.clone(), worker_pool.clone(), execution_deadline, ); + ExecutorWrapper::Concurrent(executor, worker_pool) + } else { + let executor = + TransactionExecutor::new(state, self.block_context.clone(), executor_config); + ExecutorWrapper::Sequential(executor) + }; - let results = executor.add_txs_and_wait(&txs); - let block_summary = executor.close_block(results.len()).unwrap(); + (txs, executor_wrapper) + } - drop(executor); - Arc::try_unwrap(worker_pool) - .expect("More than one instance of worker pool exists") - .join(); + /// Runs the prepared transactions on the executor. + /// Asserts that none of the transactions reverted. + /// Returns the execution results. + pub fn run_block_of_transfers( + txs: &[Transaction], + executor_wrapper: &mut ExecutorWrapper, + execution_deadline: Option, + ) -> Vec { + let results = match executor_wrapper { + ExecutorWrapper::Concurrent(ref mut executor, _) => executor.add_txs_and_wait(txs), + ExecutorWrapper::Sequential(ref mut executor) => { + executor.execute_txs(txs, execution_deadline) + } + }; - (results, block_summary) - } else { - let mut executor = - TransactionExecutor::new(state, self.block_context.clone(), executor_config); - let results = executor.execute_txs(&txs, execution_deadline); - (results, executor.finalize().unwrap()) + // Extract execution infos and validate that no transactions reverted. + results + .into_iter() + .map(|result| { + let (execution_info, _state_maps) = result.unwrap(); + assert!(!execution_info.is_reverted()); + execution_info + }) + .collect() + } + + /// Finalizes the executor and validates native execution of the transactions. + /// Returns the block execution summary and execution infos. + pub fn summarize_run_block_of_transfers( + executor_wrapper: ExecutorWrapper, + execution_infos: Vec, + cairo_version: CairoVersion, + ) -> (BlockExecutionSummary, Vec) { + // Finalize executor and get block summary. + let block_summary = match executor_wrapper { + ExecutorWrapper::Concurrent(mut executor, worker_pool) => { + let block_summary = executor.close_block(execution_infos.len()).unwrap(); + + drop(executor); + Arc::try_unwrap(worker_pool) + .expect("More than one instance of worker pool exists") + .join(); + + block_summary + } + ExecutorWrapper::Sequential(mut executor) => executor.finalize().unwrap(), + }; + + // Validate native execution. + let expected_cairo_native = cairo_version.is_cairo_native(); + for execution_info in &execution_infos { + execution_info.check_call_infos_native_execution(expected_cairo_native); } + + (block_summary, execution_infos) } /// Generates and executes transfer transactions. @@ -214,31 +288,20 @@ impl TransfersGenerator { &mut self, timeout: Option, ) -> (BlockExecutionSummary, Vec) { - let mut txs: Vec = Vec::with_capacity(self.config.n_txs); - for _ in 0..self.config.n_txs { - let sender_address = self.account_addresses[self.sender_index]; - let recipient_address = self.get_next_recipient(); - self.sender_index = (self.sender_index + 1) % self.account_addresses.len(); - - let tx = self.generate_transfer(sender_address, recipient_address); - let account_tx = AccountTransaction::new_for_sequencing(tx); - txs.push(Transaction::Account(account_tx)); - } - let execution_deadline = timeout.map(|timeout| Instant::now() + timeout); - let (results, block_summary) = self._run_txs(txs, execution_deadline); - // Execution infos of transactions that were executed. - let mut collected_execution_infos = Vec::::new(); - for result in results { - let execution_info = &result.unwrap().0; + // Prepare: generates transactions and creates executor. + let (txs, mut executor_wrapper) = self.prepare_to_run_block_of_transfers(None, timeout); - assert!(!execution_info.is_reverted()); - - let expected_cairo_native = self.config.cairo_version.is_cairo_native(); - execution_info.check_call_infos_native_execution(expected_cairo_native); - collected_execution_infos.push(execution_info.clone()); - } + // Run: executes the transactions and asserts that none of them reverted. + let execution_deadline = timeout.map(|timeout_duration| Instant::now() + timeout_duration); + let execution_infos = + Self::run_block_of_transfers(&txs, &mut executor_wrapper, execution_deadline); - (block_summary, collected_execution_infos) + // Summarize: finalizes executor, and validates native execution (if applicable). + Self::summarize_run_block_of_transfers( + executor_wrapper, + execution_infos, + self.config.cairo_version, + ) // TODO(Avi, 01/06/2024): Run the same transactions concurrently on a new state and compare // the state diffs. diff --git a/crates/blockifier/src/transaction/account_transaction.rs b/crates/blockifier/src/transaction/account_transaction.rs index 3186a126d17..df65b390f1a 100644 --- a/crates/blockifier/src/transaction/account_transaction.rs +++ b/crates/blockifier/src/transaction/account_transaction.rs @@ -564,25 +564,6 @@ impl AccountTransaction { execute_call_info = self.run_execute(state, &mut execution_context, remaining_gas)?; validate_call_info = self.validate_tx(state, tx_context.clone(), remaining_gas)?; } else { - // TODO(Meshi): Find a better way to handle tests non V3 transactions. - if tx_context.block_context.versioned_constants.block_casm_hash_v1_declares - && self.tx.version() >= TransactionVersion::THREE - { - if let Transaction::Declare(declare_tx) = &self.tx { - if let Err((class_hash, compiled_class_hash, compiled_class_hash_v2)) = - declare_tx.check_compile_class_hash_v2_declaration() - { - return Err( - TransactionExecutionError::DeclareTransactionCasmHashMissMatch { - class_hash, - compiled_class_hash, - compiled_class_hash_v2, - }, - ); - } - } - } - log::info!("apart from the deployAccount, here in the run_non_revertible"); validate_call_info = self.validate_tx(state, tx_context.clone(), remaining_gas)?; let mut execution_context = EntryPointExecutionContext::new_invoke( tx_context.clone(), diff --git a/crates/blockifier/src/transaction/account_transactions_test.rs b/crates/blockifier/src/transaction/account_transactions_test.rs index 9618a1356ee..8a7b3b956d7 100644 --- a/crates/blockifier/src/transaction/account_transactions_test.rs +++ b/crates/blockifier/src/transaction/account_transactions_test.rs @@ -893,23 +893,30 @@ fn test_fail_declare(block_context: BlockContext, max_fee: Fee) { class_hash: class_hash!(7_u64), compiled_class_hash: CompiledClassHash(8_u64.into()), ..Default::default() -}))] +}), HashVersion::V2)] +#[should_panic(expected = "DeclareTransactionCasmHashMissMatch")] +#[case::poseidon_declare_tx(DeclareTransaction::V3(DeclareTransactionV3 { + sender_address: ApiExecutableDeclareTransaction::bootstrap_address(), + class_hash: class_hash!(7_u64), + compiled_class_hash: CompiledClassHash(8_u64.into()), + ..Default::default() +}), HashVersion::V1)] #[should_panic(expected = "UninitializedStorageAddress")] #[case::wrong_tx_version(DeclareTransaction::V2(DeclareTransactionV2 { sender_address: ApiExecutableDeclareTransaction::bootstrap_address(), ..Default::default() -}))] +}), HashVersion::V2)] #[should_panic(expected = "InvalidNonce")] #[case::wrong_nonce(DeclareTransaction::V3(DeclareTransactionV3 { sender_address: ApiExecutableDeclareTransaction::bootstrap_address(), nonce: Nonce(felt!(1_u64)), ..Default::default() -}))] +}), HashVersion::V2)] #[should_panic(expected = "UninitializedStorageAddress")] #[case::wrong_sender_address(DeclareTransaction::V3(DeclareTransactionV3 { sender_address: ContractAddress(PatriciaKey::from(1_u128)), ..Default::default() -}))] +}), HashVersion::V2)] #[should_panic(expected = "InsufficientResourceBounds")] #[case::non_trivial_resource_bounds(DeclareTransaction::V3(DeclareTransactionV3 { sender_address: ApiExecutableDeclareTransaction::bootstrap_address(), @@ -919,8 +926,12 @@ fn test_fail_declare(block_context: BlockContext, max_fee: Fee) { l1_data_gas: ResourceBounds::default(), }), ..Default::default() -}))] -fn test_bootstrap_declare(block_context: BlockContext, #[case] declare_tx: DeclareTransaction) { +}), HashVersion::V2)] +fn test_bootstrap_declare( + block_context: BlockContext, + #[case] declare_tx: DeclareTransaction, + #[case] hash_version: HashVersion, +) { let class_info = calculate_class_info_for_testing( FeatureContract::Empty(CairoVersion::Cairo1(RunnableCairo1::Casm)).get_class(), ); @@ -930,10 +941,12 @@ fn test_bootstrap_declare(block_context: BlockContext, #[case] declare_tx: Decla tx_hash: TransactionHash::default(), class_info, }; - // If this is a V3 declare, override the compiled_class_hash field with v2 hash. - if let DeclareTransaction::V3(ref mut tx_v3) = executable_declare.tx { - if let ContractClass::V1((casm, _)) = contract_class { - tx_v3.compiled_class_hash = casm.hash(&HashVersion::V2); + + // Update compiled_class_hash in V3 declare txs to match the contract class with the given hash + // version. + if let DeclareTransaction::V3(tx) = &mut executable_declare.tx { + if let ContractClass::V1((casm, _)) = &contract_class { + tx.compiled_class_hash = casm.hash(&hash_version); } } let compiled_class_hash = executable_declare.tx.compiled_class_hash(); diff --git a/crates/blockifier/src/transaction/errors.rs b/crates/blockifier/src/transaction/errors.rs index 0296ce98180..6aec11f35ab 100644 --- a/crates/blockifier/src/transaction/errors.rs +++ b/crates/blockifier/src/transaction/errors.rs @@ -1,13 +1,7 @@ use cairo_vm::types::errors::program_errors::ProgramError; use num_bigint::BigUint; use starknet_api::block::GasPrice; -use starknet_api::core::{ - ClassHash, - CompiledClassHash, - ContractAddress, - EntryPointSelector, - Nonce, -}; +use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, Nonce}; use starknet_api::execution_resources::GasAmount; use starknet_api::transaction::fields::{AllResourceBounds, Fee, Resource}; use starknet_api::transaction::TransactionVersion; @@ -86,15 +80,6 @@ pub enum TransactionExecutionError { ContractConstructorExecutionFailed(#[from] ConstructorEntryPointExecutionError), #[error("Class with hash {:#066x} is already declared.", **class_hash)] DeclareTransactionError { class_hash: ClassHash }, - #[error( - "Mismatch compiled class hash for class with hash {:#064x}. Actual: {:#064x}, Expected: {:#064x}", - class_hash.0, compiled_class_hash.0, compiled_class_hash_v2.0 - )] - DeclareTransactionCasmHashMissMatch { - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - compiled_class_hash_v2: CompiledClassHash, - }, #[error("{}", gen_tx_execution_error_trace(self))] ExecutionError { error: Box, diff --git a/crates/blockifier/src/transaction/transactions.rs b/crates/blockifier/src/transaction/transactions.rs index fcf4073ca91..bd9d8798cd9 100644 --- a/crates/blockifier/src/transaction/transactions.rs +++ b/crates/blockifier/src/transaction/transactions.rs @@ -11,7 +11,12 @@ use starknet_api::executable_transaction::{ L1HandlerTransaction, }; use starknet_api::transaction::fields::{AccountDeploymentData, Calldata}; -use starknet_api::transaction::{constants, DeclareTransactionV2, DeclareTransactionV3}; +use starknet_api::transaction::{ + constants, + DeclareTransactionV2, + DeclareTransactionV3, + TransactionVersion, +}; use crate::context::{BlockContext, GasCounter, TransactionContext}; use crate::execution::call_info::CallInfo; @@ -175,7 +180,14 @@ impl Executable for DeclareTransaction { | starknet_api::transaction::DeclareTransaction::V3(DeclareTransactionV3 { compiled_class_hash, .. - }) => try_declare(self, state, class_hash, Some(*compiled_class_hash))?, + }) => { + if context.tx_context.block_context.versioned_constants.block_casm_hash_v1_declares + && self.version() >= TransactionVersion::THREE + { + self.check_compile_class_hash_v2_declaration()? + } + try_declare(self, state, class_hash, Some(*compiled_class_hash))? + } } Ok(None) } diff --git a/crates/blockifier/src/transaction/transactions_test.rs b/crates/blockifier/src/transaction/transactions_test.rs index bbf4195ce08..f0f64d61525 100644 --- a/crates/blockifier/src/transaction/transactions_test.rs +++ b/crates/blockifier/src/transaction/transactions_test.rs @@ -111,7 +111,7 @@ use crate::execution::syscalls::hint_processor::EmitEventError; use crate::execution::syscalls::hint_processor::SyscallExecutionError; #[cfg(feature = "cairo_native")] use crate::execution::syscalls::vm_syscall_utils::SyscallExecutorBaseError; -use crate::execution::syscalls::vm_syscall_utils::SyscallSelector; +use crate::execution::syscalls::vm_syscall_utils::{SyscallSelector, SyscallUsage}; use crate::fee::fee_checks::FeeCheckError; use crate::fee::fee_utils::{balance_to_big_uint, get_fee_by_gas_vector, GasVectorToL1GasForFee}; use crate::fee::gas_usage::{ @@ -132,7 +132,11 @@ use crate::state::state_api::{State, StateReader}; use crate::test_utils::contracts::FeatureContractTrait; use crate::test_utils::dict_state_reader::DictStateReader; use crate::test_utils::initial_test_state::{fund_account, test_state}; -use crate::test_utils::l1_handler::{l1_handler_set_value_and_revert, l1handler_tx}; +use crate::test_utils::l1_handler::{ + l1_handler_set_value_and_revert, + l1handler_tx, + L1_HANDLER_SET_VALUE_ENTRY_POINT_SELECTOR, +}; use crate::test_utils::prices::Prices; use crate::test_utils::test_templates::{cairo_version, two_cairo_versions}; use crate::test_utils::{ @@ -434,6 +438,23 @@ fn expected_fee_transfer_call_info( CairoVersion::Cairo0 => Prices::FeeTransfer(account_address, *fee_type).into(), CairoVersion::Cairo1(_) => ExecutionResources::default(), }; + let mut syscalls_usage = HashMap::from([ + (SyscallSelector::StorageRead, SyscallUsage::with_call_count(4)), + (SyscallSelector::StorageWrite, SyscallUsage::with_call_count(4)), + (SyscallSelector::EmitEvent, SyscallUsage::with_call_count(1)), + ]); + + match cairo_version { + CairoVersion::Cairo0 => { + syscalls_usage + .insert(SyscallSelector::GetCallerAddress, SyscallUsage::with_call_count(1)); + } + CairoVersion::Cairo1(_) => { + syscalls_usage + .insert(SyscallSelector::GetExecutionInfo, SyscallUsage::with_call_count(1)); + } + } + Some(CallInfo { call: expected_fee_transfer_call, execution: CallExecution { @@ -457,6 +478,7 @@ fn expected_fee_transfer_call_info( }, tracked_resource: expected_tracked_resource, builtin_counters, + syscalls_usage, ..Default::default() }) } @@ -691,6 +713,16 @@ fn test_invoke_tx( CairoVersion::Cairo0 => HashMap::from([(BuiltinName::range_check, 19)]), CairoVersion::Cairo1(_) => HashMap::from([(BuiltinName::range_check, 27)]), }; + let syscalls_usage = match account_cairo_version { + CairoVersion::Cairo0 => HashMap::from([( + SyscallSelector::CallContract, + SyscallUsage { call_count: 1, linear_factor: 0 }, + )]), + CairoVersion::Cairo1(_) => HashMap::from([ + (SyscallSelector::GetExecutionInfo, SyscallUsage { call_count: 1, linear_factor: 0 }), + (SyscallSelector::CallContract, SyscallUsage { call_count: 1, linear_factor: 0 }), + ]), + }; let expected_execute_call_info = Some(CallInfo { call: expected_execute_call, execution: CallExecution { @@ -703,6 +735,7 @@ fn test_invoke_tx( inner_calls: expected_inner_calls, tracked_resource, builtin_counters, + syscalls_usage, ..Default::default() }); @@ -1787,12 +1820,20 @@ fn test_declare_redeposit_amount_regression() { } #[apply(cairo_version)] -#[case(TransactionVersion::ZERO, CairoVersion::Cairo0, HashVersion::V2)] -#[case(TransactionVersion::ONE, CairoVersion::Cairo0, HashVersion::V2)] -#[case(TransactionVersion::TWO, CairoVersion::Cairo1(RunnableCairo1::Casm), HashVersion::V2)] -#[case(TransactionVersion::THREE, CairoVersion::Cairo1(RunnableCairo1::Casm), HashVersion::V2)] +#[case(TransactionVersion::ZERO, CairoVersion::Cairo0, None)] +#[case(TransactionVersion::ONE, CairoVersion::Cairo0, None)] +#[case(TransactionVersion::TWO, CairoVersion::Cairo1(RunnableCairo1::Casm), Some(HashVersion::V2))] +#[case( + TransactionVersion::THREE, + CairoVersion::Cairo1(RunnableCairo1::Casm), + Some(HashVersion::V2) +)] #[should_panic(expected = "DeclareTransactionCasmHashMissMatch")] -#[case(TransactionVersion::THREE, CairoVersion::Cairo1(RunnableCairo1::Casm), HashVersion::V1)] +#[case( + TransactionVersion::THREE, + CairoVersion::Cairo1(RunnableCairo1::Casm), + Some(HashVersion::V1) +)] fn test_declare_tx( default_all_resource_bounds: ValidResourceBounds, cairo_version: CairoVersion, @@ -1800,7 +1841,7 @@ fn test_declare_tx( #[case] empty_contract_version: CairoVersion, // Used only for V3+ transactions to check that we are blocking declare txs with V1 casm // hashes. - #[case] hash_version: HashVersion, + #[case] hash_version: Option, #[values(false, true)] use_kzg_da: bool, ) { let account_cairo_version = cairo_version; @@ -1811,6 +1852,10 @@ fn test_declare_tx( let chain_info = &block_context.chain_info; let state = &mut test_state(chain_info, BALANCE, &[(account, 1)]); let class_hash = empty_contract.get_class_hash(); + let hash_version = match hash_version { + Some(hash_version) => hash_version, + None => HashVersion::V2, + }; let compiled_class_hash = empty_contract.get_compiled_class_hash(&hash_version); let class_info = calculate_class_info_for_testing(empty_contract.get_class()); let sender_address = account.get_instance_address(0); @@ -2694,7 +2739,7 @@ fn test_l1_handler(#[values(false, true)] use_kzg_da: bool) { class_hash: Some(test_contract.get_class_hash()), code_address: None, entry_point_type: EntryPointType::L1Handler, - entry_point_selector: selector_from_name("l1_handler_set_value"), + entry_point_selector: *L1_HANDLER_SET_VALUE_ENTRY_POINT_SELECTOR, calldata: calldata.clone(), storage_address: contract_address, caller_address: ContractAddress::default(), @@ -2719,6 +2764,10 @@ fn test_l1_handler(#[values(false, true)] use_kzg_da: bool) { .get_runnable_class() .tracked_resource(&versioned_constants.min_sierra_version_for_sierra_gas, None), builtin_counters: HashMap::from([(BuiltinName::range_check, 6)]), + syscalls_usage: HashMap::from([( + SyscallSelector::StorageWrite, + SyscallUsage { call_count: 1, linear_factor: 0 }, + )]), ..Default::default() }; diff --git a/crates/mempool_test_utils/src/starknet_api_test_utils.rs b/crates/mempool_test_utils/src/starknet_api_test_utils.rs index 59ea05832fc..926346ad06a 100644 --- a/crates/mempool_test_utils/src/starknet_api_test_utils.rs +++ b/crates/mempool_test_utils/src/starknet_api_test_utils.rs @@ -8,7 +8,6 @@ use assert_matches::assert_matches; use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1}; use blockifier_test_utils::calldata::{create_calldata, create_trivial_calldata}; use blockifier_test_utils::contracts::FeatureContract; -use papyrus_base_layer::ethereum_base_layer_contract::L1ToL2MessageArgs; use papyrus_base_layer::test_utils::DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS; use starknet_api::abi::abi_utils::selector_from_name; use starknet_api::block::GasPrice; @@ -52,7 +51,8 @@ use crate::{COMPILED_CLASS_HASH_OF_CONTRACT_CLASS, CONTRACT_CLASS_FILE, TEST_FIL pub const VALID_L1_GAS_MAX_AMOUNT: u64 = 203484; pub const VALID_L1_GAS_MAX_PRICE_PER_UNIT: u128 = 100000000000000; -pub const VALID_L2_GAS_MAX_AMOUNT: u64 = 500000 * 200000; // Enough to declare the test class. +// Enough to declare the test class, but under the OS's upper limit. +pub const VALID_L2_GAS_MAX_AMOUNT: u64 = 1_100_000_000; pub const VALID_L2_GAS_MAX_PRICE_PER_UNIT: u128 = 100000000000000; pub const VALID_L1_DATA_GAS_MAX_AMOUNT: u64 = 203484; pub const VALID_L1_DATA_GAS_MAX_PRICE_PER_UNIT: u128 = 100000000000000; @@ -170,34 +170,34 @@ pub type AccountId = usize; type SharedNonceManager = Rc>; +#[derive(Debug, Default)] struct L1HandlerTransactionGenerator { - // The L1 nonce for the next created L1 handler transaction. - l1_tx_nonce: u64, -} - -impl Default for L1HandlerTransactionGenerator { - /// The Anvil instance is spawned with a nonce of 1 for the account [Self::L1_ACCOUNT_ADDRESS]. - fn default() -> Self { - Self { l1_tx_nonce: 1 } - } + n_generated_txs: usize, } impl L1HandlerTransactionGenerator { const L1_ACCOUNT_ADDRESS: StarkHash = DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS; - /// Creates an L1 handler transaction calling the "l1_handler_set_value" entry point in + /// Creates an L1 handler transaction calling either "l1_handler_set_value" or + /// "l1_handler_set_value_and_revert" entry point in /// [TestContract](FeatureContract::TestContract). - fn create_l1_to_l2_message_args(&mut self) -> L1ToL2MessageArgs { - let l1_tx_nonce = self.l1_tx_nonce; - self.l1_tx_nonce += 1; + fn create_l1_to_l2_message_args(&mut self, should_revert: bool) -> L1HandlerTransaction { + self.n_generated_txs += 1; + // TODO(Arni): Get test contract from test setup. let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)); - let l1_handler_tx = L1HandlerTransaction { + // TODO(Arni): Consider saving this value as a lazy constant. + let entry_point_selector = if should_revert { + selector_from_name("l1_handler_set_value_and_revert") + } else { + selector_from_name("l1_handler_set_value") + }; + + L1HandlerTransaction { contract_address: test_contract.get_instance_address(0), - // TODO(Arni): Consider saving this value as a lazy constant. - entry_point_selector: selector_from_name("l1_handler_set_value"), + entry_point_selector, calldata: calldata![ Self::L1_ACCOUNT_ADDRESS, // Arbitrary key and value. @@ -205,13 +205,7 @@ impl L1HandlerTransactionGenerator { felt!("0x44") // value ], ..Default::default() - }; - - L1ToL2MessageArgs { tx: l1_handler_tx, l1_tx_nonce } - } - - fn n_generated_txs(&self) -> u64 { - self.l1_tx_nonce - 1 + } } } @@ -289,8 +283,9 @@ impl MultiAccountTransactionGenerator { contract_address_salt: tx_gen.contract_address_salt, }) .collect(); - let l1_handler_tx_generator = - L1HandlerTransactionGenerator { l1_tx_nonce: self.l1_handler_tx_generator.l1_tx_nonce }; + let l1_handler_tx_generator = L1HandlerTransactionGenerator { + n_generated_txs: self.l1_handler_tx_generator.n_generated_txs, + }; Self { account_tx_generators, nonce_manager, l1_handler_tx_generator } } @@ -372,15 +367,12 @@ impl MultiAccountTransactionGenerator { .collect() } - pub fn create_l1_to_l2_message_args(&mut self) -> L1ToL2MessageArgs { - self.l1_handler_tx_generator.create_l1_to_l2_message_args() + pub fn create_l1_to_l2_message_args(&mut self, should_revert: bool) -> L1HandlerTransaction { + self.l1_handler_tx_generator.create_l1_to_l2_message_args(should_revert) } pub fn n_l1_txs(&self) -> usize { - self.l1_handler_tx_generator - .n_generated_txs() - .try_into() - .expect("Failed to convert nonce to usize") + self.l1_handler_tx_generator.n_generated_txs } } diff --git a/crates/native_blockifier/src/py_block_executor.rs b/crates/native_blockifier/src/py_block_executor.rs index c805a564fd6..c2467475db9 100644 --- a/crates/native_blockifier/src/py_block_executor.rs +++ b/crates/native_blockifier/src/py_block_executor.rs @@ -2,7 +2,7 @@ use std::str::FromStr; -use apollo_state_reader::papyrus_state::PapyrusReader; +use apollo_state_reader::apollo_state::ApolloReader; use blockifier::blockifier::config::{ContractClassManagerConfig, TransactionExecutorConfig}; use blockifier::blockifier::transaction_executor::{ BlockExecutionSummary, @@ -79,7 +79,7 @@ pub struct PyBlockExecutor { pub tx_executor_config: TransactionExecutorConfig, pub chain_info: ChainInfo, pub versioned_constants: VersionedConstants, - pub tx_executor: Option>>, + pub tx_executor: Option>>, /// `Send` trait is required for `pyclass` compatibility as Python objects must be threadsafe. pub storage: Box, pub contract_class_manager: ContractClassManager, @@ -339,6 +339,11 @@ impl PyBlockExecutor { self.versioned_constants.enable_casm_hash_migration = enable_casm_hash_migration; } + #[pyo3(signature = (block_casm_hash_v1_declares))] + pub fn set_block_casm_hash_v1_declares_in_vc(&mut self, block_casm_hash_v1_declares: bool) { + self.versioned_constants.block_casm_hash_v1_declares = block_casm_hash_v1_declares; + } + #[pyo3(signature = (concurrency_config, contract_class_manager_config, os_config, path, max_state_diff_size, stack_size, min_sierra_version, enable_casm_hash_migration))] #[staticmethod] #[allow(clippy::too_many_arguments)] @@ -394,20 +399,20 @@ impl PyBlockExecutor { impl PyBlockExecutor { pub fn tx_executor( &mut self, - ) -> &mut TransactionExecutor> { + ) -> &mut TransactionExecutor> { self.tx_executor.as_mut().expect("Transaction executor should be initialized") } fn get_aligned_reader( &self, next_block_number: BlockNumber, - ) -> StateReaderAndContractManager { + ) -> StateReaderAndContractManager { // Full-node storage must be aligned to the Python storage before initializing a reader. self.storage.validate_aligned(next_block_number.0); - let papyrus_reader = PapyrusReader::new(self.storage.reader().clone(), next_block_number); + let apollo_reader = ApolloReader::new(self.storage.reader().clone(), next_block_number); StateReaderAndContractManager { - state_reader: papyrus_reader, + state_reader: apollo_reader, contract_class_manager: self.contract_class_manager.clone(), } } diff --git a/crates/papyrus_base_layer/Cargo.toml b/crates/papyrus_base_layer/Cargo.toml index d2d8ebe8c3c..be307a0fb89 100644 --- a/crates/papyrus_base_layer/Cargo.toml +++ b/crates/papyrus_base_layer/Cargo.toml @@ -9,23 +9,19 @@ license-file.workspace = true workspace = true [features] -testing = ["alloy/node-bindings", "colored", "tar", "tempfile"] +testing = ["alloy/node-bindings"] [dependencies] -alloy = { workspace = true, features = ["contract", "json-rpc", "rpc-types"] } +alloy = { workspace = true, features = ["contract", "json-rpc", "node-bindings", "rpc-types"] } apollo_config.workspace = true apollo_l1_endpoint_monitor_types.workspace = true async-trait.workspace = true -colored = { workspace = true, optional = true } -ethers.workspace = true futures.workspace = true hex.workspace = true mockall.workspace = true serde.workspace = true starknet-types-core.workspace = true starknet_api.workspace = true -tar = { workspace = true, optional = true } -tempfile = { workspace = true, optional = true } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } tracing.workspace = true @@ -33,13 +29,8 @@ url = { workspace = true, features = ["serde"] } validator.workspace = true [dev-dependencies] -alloy = { workspace = true, features = ["node-bindings"] } apollo_l1_endpoint_monitor_types = { workspace = true, features = ["testing"] } assert_matches.workspace = true -colored.workspace = true -ethers-core.workspace = true pretty_assertions.workspace = true starknet-types-core.workspace = true starknet_api = { workspace = true, features = ["testing"] } -tar.workspace = true -tempfile.workspace = true diff --git a/crates/papyrus_base_layer/resources/StarknetForSequencerTesting.json b/crates/papyrus_base_layer/resources/StarknetForSequencerTesting.json new file mode 100644 index 00000000000..b368b9e1f44 --- /dev/null +++ b/crates/papyrus_base_layer/resources/StarknetForSequencerTesting.json @@ -0,0 +1,557 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "ConsumedMessageToL1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "ConsumedMessageToL2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "LogMessageToL1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "LogMessageToL2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "globalRoot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "blockNumber", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "blockHash", + "type": "uint256" + } + ], + "name": "LogStateUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2Canceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2CancellationStarted", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "cancelL1ToL2Message", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "configHash", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "consumeMessageFromL2", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeCollector", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxL1MsgFee", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "initData", "type": "bytes" } + ], + "name": "initializeMock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l1ToL2MessageCancellations", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ToL2MessageNonce", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l1ToL2Messages", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "l1ToL2MsgHash", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l2ToL1Messages", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "l2ToL1MsgHash", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "messageCancellationDelay", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "programHash", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "sendMessageToL2", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "startL1ToL2MessageCancellation", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockHash", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockNumber", + "outputs": [ + { "internalType": "int256", "name": "", "type": "int256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateRoot", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "programOutput", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "onchainDataSize", + "type": "uint256" + } + ], + "name": "updateState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600e575f5ffd5b506120428061001c5f395ff3fe60806040526004361061011b575f3560e01c806377c7d7a91161009d5780639be446bf116100625780639be446bf146102c7578063a46efaf3146102e6578063b64b673714610305578063c415b95c14610324578063e1f1176d14610350575f5ffd5b806377c7d7a91461024d5780637a98660b1461026c5780638303bd8a1461028b5780638a9bf0901461029f5780639588eca2146102b3575f5ffd5b80633e3aa6c5116100e35780633e3aa6c5146101ac57806354eccba4146101d45780636170ff1b146101ee57806363142fb81461020d5780636e107a8c1461022e575f5ffd5b8063018cccdf1461011f5780632c9dd5c01461014657806335befa5d14610165578063382d83e3146101795780633d8a5df81461018d575b5f5ffd5b34801561012a575f5ffd5b50610133610364565b6040519081526020015b60405180910390f35b348015610151575f5ffd5b506101336101603660046119b0565b6103a8565b348015610170575f5ffd5b5061013361049b565b348015610184575f5ffd5b506101336104ad565b348015610198575f5ffd5b506101336101a7366004611a0e565b6104bf565b6101bf6101ba366004611a64565b610503565b6040805192835260208301919091520161013d565b3480156101df575f5ffd5b50670de0b6b3a7640000610133565b3480156101f9575f5ffd5b50610133610208366004611a9b565b610694565b348015610218575f5ffd5b5061022c610227366004611af1565b6108a8565b005b348015610239575f5ffd5b5061022c610248366004611b39565b610990565b348015610258575f5ffd5b50610133610267366004611ba7565b6109fa565b348015610277575f5ffd5b50610133610286366004611a9b565b610a13565b348015610296575f5ffd5b50610133610aed565b3480156102aa575f5ffd5b50610133610b0f565b3480156102be575f5ffd5b50610133610b31565b3480156102d2575f5ffd5b506101336102e1366004611ba7565b610b40565b3480156102f1575f5ffd5b50610133610300366004611ba7565b610b49565b348015610310575f5ffd5b5061013361031f366004611bbe565b610b52565b34801561032f575f5ffd5b50610338610b9c565b6040516001600160a01b03909116815260200161013d565b34801561035b575f5ffd5b50610133610bdb565b5f6103a36040518060400160405280602081526020017f535441524b4e45545f312e305f4d5347494e475f4c31544f4c325f4e4f4e4345815250610bf9565b905090565b5f5f6103b6853386866104bf565b90505f6103c1610c2c565b5f8381526020919091526040902054116104225760405162461bcd60e51b815260206004820152601a60248201527f494e56414c49445f4d4553534147455f544f5f434f4e53554d4500000000000060448201526064015b60405180910390fd5b336001600160a01b0316857f7a06c571aa77f34d9706c51e5d8122b5595aebeaa34233bfe866f22befb973b1868660405161045e929190611c59565b60405180910390a36001610470610c2c565b5f8381526020019081526020015f205f82825461048d9190611c88565b909155509095945050505050565b5f6104a4610c4e565b60010154905090565b5f6104b6610c4e565b60020154905090565b6040515f906104e39086906001600160a01b03871690859087908290602001611cc2565b604051602081830303815290604052805190602001209050949350505050565b5f5f5f341161055e5760405162461bcd60e51b815260206004820152602160248201527f4c315f4d53475f4645455f4d5553545f42455f475245415445525f5448414e5f6044820152600360fc1b6064820152608401610419565b670de0b6b3a76400003411156105b65760405162461bcd60e51b815260206004820152601760248201527f4d41585f4c315f4d53475f4645455f45584345454445440000000000000000006044820152606401610419565b5f6105bf610364565b905061060c6040518060400160405280602081526020017f535441524b4e45545f312e305f4d5347494e475f4c31544f4c325f4e4f4e43458152508260016106079190611ceb565b610c97565b8587336001600160a01b03167fdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b8888863460405161064d9493929190611cfe565b60405180910390a45f610664338989898987610b52565b9050610671346001611ceb565b610679610cc9565b5f838152602091909152604090205597909650945050505050565b5f8486336001600160a01b03167f8abd2ec2e0a10c82f5b60ea00455fa96c41fd144f225fcc52b8d83d94f803ed88787876040516106d493929190611d24565b60405180910390a45f6106eb338888888888610b52565b90505f6106f6610cc9565b5f8381526020919091526040812054915081900361074d5760405162461bcd60e51b81526020600482015260146024820152731393d7d35154d4d051d157d513d7d0d05390d15360621b6044820152606401610419565b5f610756610ceb565b5f848152602091909152604081205491508190036107c15760405162461bcd60e51b815260206004820152602260248201527f4d4553534147455f43414e43454c4c4154494f4e5f4e4f545f52455155455354604482015261115160f21b6064820152608401610419565b5f6107ca610aed565b6107d49083611ceb565b9050818110156108265760405162461bcd60e51b815260206004820152601c60248201527f43414e43454c5f414c4c4f5745445f54494d455f4f564552464c4f57000000006044820152606401610419565b804210156108825760405162461bcd60e51b8152602060048201526024808201527f4d4553534147455f43414e43454c4c4154494f4e5f4e4f545f414c4c4f57454460448201526317d6515560e21b6064820152608401610419565b5f61088b610cc9565b5f8681526020919091526040902055509198975050505050505050565b600a82116108f85760405162461bcd60e51b815260206004820152601960248201527f535441524b4e45545f4f55545055545f544f4f5f53484f5254000000000000006044820152606401610419565b61090c8383610905610c4e565b9190610d0d565b8282600881811061091f5761091f611d47565b905060200201355f1461096d5760405162461bcd60e51b8152602060048201526016602482015275554e45585045435445445f4b5a475f44415f464c414760501b6044820152606401610419565b6109778383610da1565b61098b8383610984610c4e565b9190610f3c565b505050565b610998610f9d565b156109e65780156109e25760405162461bcd60e51b8152602060048201526014602482015273554e45585045435445445f494e49545f4441544160601b6044820152606401610419565b5050565b6109f08282610fad565b6109e28282611057565b5f610a03610cc9565b5f92835260205250604090205490565b5f8486336001600160a01b03167f2e00dccd686fd6823ec7dc3e125582aa82881b6ff5f6b5a73856e1ea8338a3be878787604051610a5393929190611d24565b60405180910390a45f610a6a338888888888610b52565b90505f610a75610cc9565b5f8381526020919091526040902054905080610aca5760405162461bcd60e51b81526020600482015260146024820152731393d7d35154d4d051d157d513d7d0d05390d15360621b6044820152606401610419565b42610ad3610ceb565b5f8481526020919091526040902055509695505050505050565b5f6103a36040518060600160405280602d8152602001611f49602d9139610bf9565b5f6103a3604051806060016040528060268152602001611f2360269139610bf9565b5f610b3a610c4e565b54919050565b5f610a03610ceb565b5f610a03610c2c565b6040515f90610b7a906001600160a01b0389169088908590899088908a908290602001611d5b565b6040516020818303038152906040528051906020012090509695505050505050565b5f6103a36040518060400160405280601f81526020017f4645455f434f4c4c454354494f4e5f414444524553535f534c4f545f54414700815250610bf9565b5f6103a3604051806060016040528060248152602001611f76602491395b5f5f82604051602001610c0c9190611d92565b60408051601f198184030181529190528051602090910120549392505050565b5f6103a3604051806060016040528060238152602001611f9a602391396110b5565b5f5f6040518060600160405280602a8152602001611fbd602a9139604051602001610c799190611d92565b60408051601f19818403018152919052805160209091012092915050565b5f82604051602001610ca99190611d92565b604051602081830303815290604052805190602001209050818155505050565b5f6103a3604051806060016040528060268152602001611fe7602691396110b5565b5f6103a3604051806060016040528060308152602001611ef3603091396110b5565b5f83600101545f1903610d2c575067080000000000001160c01b610d33565b5060018301545b8083836002818110610d4757610d47611d47565b9050602002013514610d9b5760405162461bcd60e51b815260206004820152601960248201527f494e56414c49445f505245565f424c4f434b5f4e554d424552000000000000006044820152606401610419565b50505050565b610dab82826110e7565b81816009818110610dbe57610dbe611d47565b905060200201355f14610e135760405162461bcd60e51b815260206004820152601960248201527f46554c4c5f4f55545055545f4e4f545f535550504f52544544000000000000006044820152606401610419565b610e278282610e20610c4e565b919061117a565b5f610e328383611290565b90505f610e3d61132d565b9050610e5e6001610e5085858189611da8565b610e58610c2c565b85611353565b610e689083611ceb565b9150610e825f610e7a85858189611da8565b610e58610cc9565b610e8c9083611ceb565b9150828214610edd5760405162461bcd60e51b815260206004820152601860248201527f535441524b4e45545f4f55545055545f544f4f5f4c4f4e4700000000000000006044820152606401610419565b5f610ee6610c4e565b805460018201546002830154604080519384526020840192909252908201529091507fd342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576c9060600160405180910390a15050505050565b81816003818110610f4f57610f4f611d47565b9050602002013583600101541461098b5760405162461bcd60e51b81526020600482015260126024820152715245454e5452414e43595f4641494c55524560701b6044820152606401610419565b5f610fa6610b0f565b1515919050565b60e08114610ff65760405162461bcd60e51b8152602060048201526016602482015275494c4c4547414c5f494e49545f444154415f53495a4560501b6044820152606401610419565b5f6110046020828486611dd3565b8101906110119190611ba7565b9050805f0361098b5760405162461bcd60e51b81526020600482015260126024820152712120a22fa4a724aa24a0a624ad20aa24a7a760711b6044820152606401610419565b5f808061106684860186611dfa565b925092509250611075836118ff565b61109c81611081610c4e565b90805182556020810151600183015560400151600290910155565b6110a582611924565b6110ae5f611946565b5050505050565b5f5f826040516020016110c89190611d92565b60408051601f1981840301815291905280516020909101209392505050565b6001600167080000000000001160c01b01602083028401845b818110156111205782813510611118575f9350611120565b602001611100565b5050508061098b5760405162461bcd60e51b815260206004820152602160248201527f50524f4752414d5f4f55545055545f56414c55455f4f55545f4f465f52414e476044820152604560f81b6064820152608401610419565b611185838383610d0d565b5f8282600381811061119957611199611d47565b905060200201359050836001015481136111f55760405162461bcd60e51b815260206004820152601860248201527f494e56414c49445f4e45575f424c4f434b5f4e554d42455200000000000000006044820152606401610419565b6001840181905560028401548383600481811061121457611214611d47565b90506020020135146112685760405162461bcd60e51b815260206004820152601760248201527f494e56414c49445f505245565f424c4f434b5f484153480000000000000000006044820152606401610419565b8282600581811061127b5761127b611d47565b90506020020135846002018190555050505050565b5f828260088181106112a4576112a4611d47565b905060200201355f036112b95750600a611327565b5f83836112c86001600a611ceb565b8181106112d7576112d7611d47565b9050602002013590508060026112ed9190611e83565b6112f8826002611e83565b611304600a6001611ceb565b61130f906001611ceb565b6113199190611ceb565b6113239190611ceb565b9150505b92915050565b5f5f611337610b9c565b90506001600160a01b03811661134e573391505090565b919050565b5f5f85855f81811061136757611367611d47565b905060200201359050634000000081106113c35760405162461bcd60e51b815260206004820152601c60248201527f494e56414c49445f4d4553534147455f5345474d454e545f53495a45000000006044820152606401610419565b60015f6113d08383611ceb565b90505f896113df5760046113e2565b60025b90505f5b82841015611804575f806113fa8487611ceb565b90508a811061143f5760405162461bcd60e51b8152602060048201526011602482015270135154d4d051d157d513d3d7d4d213d495607a1b6044820152606401610419565b5f8c8c8381811061145257611452611d47565b905060200201359050634000000081106114a75760405162461bcd60e51b81526020600482015260166024820152750929cac82989288bea082b2989e8288be988a9c8ea8960531b6044820152606401610419565b806114b3836001611ceb565b6114bd9190611ceb565b92508b83111561150f5760405162461bcd60e51b815260206004820152601960248201527f5452554e43415445445f4d4553534147455f5041594c4f4144000000000000006044820152606401610419565b50508b1561161b575f61152482878d8f611da8565b604051602001611535929190611e9a565b6040516020818303038152906040528051906020012090508b8b60018861155c9190611ceb565b81811061156b5761156b611d47565b905060200201356001600160a01b03168c8c5f896115899190611ceb565b81811061159857611598611d47565b905060200201357f4264ac208b5fde633ccdd42e0f12c3d6d443a4f3779bbf886925b94665b63a228e8e60038b6115cf9190611ceb565b6115db92889290611da8565b6040516115e9929190611c59565b60405180910390a35f81815260208b90526040812080546001929061160f908490611ceb565b909155506117fd915050565b5f61162882878d8f611da8565b604051602001611639929190611e9a565b60408051601f1981840301815291815281516020928301205f818152928d9052912054909150806116ac5760405162461bcd60e51b815260206004820152601a60248201527f494e56414c49445f4d4553534147455f544f5f434f4e53554d450000000000006044820152606401610419565b6116b7600182611c88565b6116c19085611ceb565b5f92835260208c90526040832083905593508c90508b6116e2600289611ceb565b8181106116f1576116f1611d47565b9050602002013590505f8c8c60058961170a9190611ceb565b61171692869290611da8565b808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152509293508f92508e9150611759905060038a611ceb565b81811061176857611768611d47565b905060200201358d8d60018a61177e9190611ceb565b81811061178d5761178d611d47565b905060200201358e8e5f8b6117a29190611ceb565b8181106117b1576117b1611d47565b905060200201356001600160a01b03167f9592d37825c744e33fa80c469683bbd04d336241bb600b574758efd182abe26a84866040516117f2929190611ea6565b60405180910390a450505b93506113e6565b8284146118535760405162461bcd60e51b815260206004820152601c60248201527f494e56414c49445f4d4553534147455f5345474d454e545f53495a45000000006044820152606401610419565b80156118f0575f876001600160a01b0316826040515f6040518083038185875af1925050503d805f81146118a2576040519150601f19603f3d011682016040523d82523d5f602084013e6118a7565b606091505b50509050806118ee5760405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b6044820152606401610419565b505b50919998505050505050505050565b611921604051806060016040528060268152602001611f236026913982610c97565b50565b611921604051806060016040528060248152602001611f766024913982610c97565b6119216040518060600160405280602d8152602001611f49602d913982610c97565b5f5f83601f840112611978575f5ffd5b50813567ffffffffffffffff81111561198f575f5ffd5b6020830191508360208260051b85010111156119a9575f5ffd5b9250929050565b5f5f5f604084860312156119c2575f5ffd5b83359250602084013567ffffffffffffffff8111156119df575f5ffd5b6119eb86828701611968565b9497909650939450505050565b80356001600160a01b038116811461134e575f5ffd5b5f5f5f5f60608587031215611a21575f5ffd5b84359350611a31602086016119f8565b9250604085013567ffffffffffffffff811115611a4c575f5ffd5b611a5887828801611968565b95989497509550505050565b5f5f5f5f60608587031215611a77575f5ffd5b8435935060208501359250604085013567ffffffffffffffff811115611a4c575f5ffd5b5f5f5f5f5f60808688031215611aaf575f5ffd5b8535945060208601359350604086013567ffffffffffffffff811115611ad3575f5ffd5b611adf88828901611968565b96999598509660600135949350505050565b5f5f5f60408486031215611b03575f5ffd5b833567ffffffffffffffff811115611b19575f5ffd5b611b2586828701611968565b909790965060209590950135949350505050565b5f5f60208385031215611b4a575f5ffd5b823567ffffffffffffffff811115611b60575f5ffd5b8301601f81018513611b70575f5ffd5b803567ffffffffffffffff811115611b86575f5ffd5b856020828401011115611b97575f5ffd5b6020919091019590945092505050565b5f60208284031215611bb7575f5ffd5b5035919050565b5f5f5f5f5f5f60a08789031215611bd3575f5ffd5b611bdc876119f8565b95506020870135945060408701359350606087013567ffffffffffffffff811115611c05575f5ffd5b611c1189828a01611968565b979a9699509497949695608090950135949350505050565b8183525f6001600160fb1b03831115611c40575f5ffd5b8260051b80836020870137939093016020019392505050565b602081525f611c6c602083018486611c29565b949350505050565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561132757611327611c74565b5f6001600160fb1b03831115611caf575f5ffd5b8260051b80838637939093019392505050565b8581528460208201528360408201525f611ce0606083018486611c9b565b979650505050505050565b8082018082111561132757611327611c74565b606081525f611d11606083018688611c29565b6020830194909452506040015292915050565b604081525f611d37604083018587611c29565b9050826020830152949350505050565b634e487b7160e01b5f52603260045260245ffd5b8781528660208201528560408201528460608201528360808201525f611d8560a083018486611c9b565b9998505050505050505050565b5f82518060208501845e5f920191825250919050565b5f5f85851115611db6575f5ffd5b83861115611dc2575f5ffd5b5050600583901b0193919092039150565b5f5f85851115611de1575f5ffd5b83861115611ded575f5ffd5b5050820193919092039150565b5f5f5f83850360a0811215611e0d575f5ffd5b84359350602085013592506060603f1982011215611e29575f5ffd5b506040516060810181811067ffffffffffffffff82111715611e5957634e487b7160e01b5f52604160045260245ffd5b60409081528581013582526060860135602083015260809095013594810194909452509093909250565b808202811582820484141761132757611327611c74565b5f611c6c828486611c9b565b604080825283519082018190525f9060208501906060840190835b81811015611edf578351835260209384019390920191600101611ec1565b505060209390930193909352509291505056fe535441524b4e45545f312e305f4d5347494e475f4c31544f4c325f43414e43454c4c4154494f4e5f4d41505050494e474d4f434b5f535441524b4e45542e305f494e49545f50524f4752414d5f484153485f55494e54535441524b4e45545f312e305f4d5347494e475f4c31544f4c325f43414e43454c4c4154494f4e5f44454c41594d4f434b5f535441524b4e45542e305f535441524b4e45545f434f4e4649475f48415348535441524b4e45545f312e305f4d5347494e475f4c32544f4c315f4d41505050494e474d4f434b5f535441524b4e45542e305f494e49545f535441524b4e45545f53544154455f535452554354535441524b4e45545f312e305f4d5347494e475f4c31544f4c325f4d41505050494e475f5632a2646970667358221220cce486760b836245e57f35ea6d7fdaa777d8d1d79675db468e4fd26b8605736764736f6c634300081e0033" +} diff --git a/crates/papyrus_base_layer/resources/ganache-db.tar b/crates/papyrus_base_layer/resources/ganache-db.tar deleted file mode 100644 index f9a64daef43..00000000000 Binary files a/crates/papyrus_base_layer/resources/ganache-db.tar and /dev/null differ diff --git a/crates/papyrus_base_layer/src/base_layer_test.rs b/crates/papyrus_base_layer/src/base_layer_test.rs index 3b7c3f53048..23970b42624 100644 --- a/crates/papyrus_base_layer/src/base_layer_test.rs +++ b/crates/papyrus_base_layer/src/base_layer_test.rs @@ -3,27 +3,11 @@ use alloy::primitives::B256; use alloy::providers::mock::Asserter; use alloy::providers::{Provider, ProviderBuilder}; use alloy::rpc::types::{Block, BlockTransactions, Header as AlloyRpcHeader}; -use assert_matches::assert_matches; use pretty_assertions::assert_eq; -use starknet_api::block::{BlockHash, BlockHashAndNumber, BlockNumber}; -use starknet_api::core::EntryPointSelector; -use starknet_api::transaction::L1HandlerTransaction; -use starknet_api::{calldata, contract_address, felt}; use url::Url; -use crate::constants::{EventIdentifier, LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER}; -use crate::ethereum_base_layer_contract::{ - EthereumBaseLayerConfig, - EthereumBaseLayerContract, - L1ToL2MessageArgs, - Starknet, -}; -use crate::test_utils::{ - anvil_instance_from_url, - ethereum_base_layer_config_for_anvil, - DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS, -}; -use crate::{BaseLayerContract, L1Event}; +use crate::ethereum_base_layer_contract::{EthereumBaseLayerContract, Starknet}; +use crate::BaseLayerContract; // TODO(Gilad): Use everywhere instead of relying on the confusing `#[ignore]` api to mark slow // tests. @@ -35,7 +19,7 @@ fn base_layer_with_mocked_provider() -> (EthereumBaseLayerContract, Asserter) { // See alloy docs, functions as a queue of mocked responses, success or failure. let asserter = Asserter::new(); - let provider = ProviderBuilder::new().on_mocked_client(asserter.clone()).root().clone(); + let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone()).root().clone(); let contract = Starknet::new(Default::default(), provider); let base_layer = EthereumBaseLayerContract { contract, @@ -46,40 +30,6 @@ fn base_layer_with_mocked_provider() -> (EthereumBaseLayerContract, Asserter) { (base_layer, asserter) } -#[tokio::test] -// Note: the test requires ganache-cli installed, otherwise it is ignored. -async fn latest_proved_block_ethereum() { - if !in_ci() { - return; - } - #[allow(deprecated)] // Legacy code, will be removed soon, don't add new instances if this. - let (node_handle, starknet_contract_address) = crate::test_utils::get_test_ethereum_node(); - let contract = EthereumBaseLayerContract::new( - EthereumBaseLayerConfig { starknet_contract_address, ..Default::default() }, - node_handle.0.endpoint().parse().unwrap(), - ); - - let first_sn_state_update = - BlockHashAndNumber { number: BlockNumber(100), hash: BlockHash(felt!("0x100")) }; - let second_sn_state_update = - BlockHashAndNumber { number: BlockNumber(200), hash: BlockHash(felt!("0x200")) }; - let third_sn_state_update = - BlockHashAndNumber { number: BlockNumber(300), hash: BlockHash(felt!("0x300")) }; - - type Scenario = (u64, Option); - let scenarios: Vec = vec![ - (0, Some(third_sn_state_update)), - (5, Some(third_sn_state_update)), - (15, Some(second_sn_state_update)), - (25, Some(first_sn_state_update)), - (1000, None), - ]; - for (scenario, expected) in scenarios { - let latest_block = contract.latest_proved_block(scenario).await.unwrap(); - assert_eq!(latest_block, expected); - } -} - #[tokio::test] async fn get_gas_price_and_timestamps() { if !in_ci() { @@ -120,75 +70,3 @@ async fn get_gas_price_and_timestamps() { let expected_original_blob_calc = 19; assert_eq!(header.blob_fee, expected_original_blob_calc); } - -#[tokio::test] -async fn events_from_other_contract() { - if !in_ci() { - return; - } - const EVENT_IDENTIFIERS: &[EventIdentifier] = &[LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER]; - - let (this_config, this_url) = ethereum_base_layer_config_for_anvil(None); - let _anvil = anvil_instance_from_url(&this_url); - let this_contract = EthereumBaseLayerContract::new(this_config.clone(), this_url.clone()); - - // Test: get_proved_block_at_unknown_block_number. - // TODO(Arni): turn this into a unit test, with its own anvil instance. - assert!( - this_contract - .get_proved_block_at(123) - .await - .unwrap_err() - // This error is nested way too deep inside `alloy`. - .to_string() - .contains("BlockOutOfRangeError") - ); - - // Test: Get events from L1 contract and other instances of this L1 contract. - // Setup. - - // Deploy the contract to the anvil instance. - Starknet::deploy(this_contract.contract.provider().clone()).await.unwrap(); - // Deploy another instance of the contract to the same anvil instance. - let other_contract = Starknet::deploy(this_contract.contract.provider().clone()).await.unwrap(); - assert_ne!( - this_contract.contract.address(), - other_contract.address(), - "The two contracts should be different." - ); - - let this_l1_handler = L1HandlerTransaction { - contract_address: contract_address!("0x12"), - entry_point_selector: EntryPointSelector(felt!("0x34")), - calldata: calldata!(DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS, felt!("0x1"), felt!("0x2")), - ..Default::default() - }; - let this_receipt = this_contract - .contract - .send_message_to_l2(&L1ToL2MessageArgs { tx: this_l1_handler.clone(), l1_tx_nonce: 2 }) - .await; - assert!(this_receipt.status()); - let this_block_number = this_receipt.block_number.unwrap(); - - let other_l1_handler = L1HandlerTransaction { - contract_address: contract_address!("0x56"), - entry_point_selector: EntryPointSelector(felt!("0x78")), - calldata: calldata!(DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS, felt!("0x1"), felt!("0x2")), - ..Default::default() - }; - let other_receipt = other_contract - .send_message_to_l2(&L1ToL2MessageArgs { tx: other_l1_handler.clone(), l1_tx_nonce: 3 }) - .await; - assert!(other_receipt.status()); - let other_block_number = other_receipt.block_number.unwrap(); - - let min_block_number = this_block_number.min(other_block_number).saturating_sub(1); - let max_block_number = this_block_number.max(other_block_number).saturating_add(1); - - // Test the events. - let mut events = - this_contract.events(min_block_number..=max_block_number, EVENT_IDENTIFIERS).await.unwrap(); - - assert_eq!(events.len(), 1, "Expected only events from this contract."); - assert_matches!(events.remove(0), L1Event::LogMessageToL2 { tx, .. } if tx == this_l1_handler); -} diff --git a/crates/papyrus_base_layer/src/constants.rs b/crates/papyrus_base_layer/src/constants.rs index f3551406bbe..274ea53eea0 100644 --- a/crates/papyrus_base_layer/src/constants.rs +++ b/crates/papyrus_base_layer/src/constants.rs @@ -5,7 +5,7 @@ use crate::ethereum_base_layer_contract::Starknet; pub type EventIdentifier = &'static str; pub const LOG_MESSAGE_TO_L2_EVENT_IDENTIFIER: &str = Starknet::LogMessageToL2::SIGNATURE; -pub const CONSUMED_MESSAGE_TO_L1_EVENT_IDENTIFIER: &str = Starknet::ConsumedMessageToL1::SIGNATURE; +pub const CONSUMED_MESSAGE_TO_L2_EVENT_IDENTIFIER: &str = Starknet::ConsumedMessageToL2::SIGNATURE; pub const MESSAGE_TO_L2_CANCELLATION_STARTED_EVENT_IDENTIFIER: &str = Starknet::MessageToL2CancellationStarted::SIGNATURE; pub const MESSAGE_TO_L2_CANCELED_EVENT_IDENTIFIER: &str = Starknet::MessageToL2Canceled::SIGNATURE; diff --git a/crates/papyrus_base_layer/src/eth_events.rs b/crates/papyrus_base_layer/src/eth_events.rs index 51d1c291bd0..af61af9dec7 100644 --- a/crates/papyrus_base_layer/src/eth_events.rs +++ b/crates/papyrus_base_layer/src/eth_events.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use alloy::primitives::{Address as EthereumContractAddress, U256}; +use alloy::primitives::U256; use alloy::rpc::types::Log; use alloy::sol_types::SolEventInterface; use starknet_api::block::BlockTimestamp; @@ -12,6 +12,7 @@ use starknet_types_core::felt::Felt; use crate::ethereum_base_layer_contract::{ EthereumBaseLayerError, EthereumBaseLayerResult, + EthereumContractAddress, Starknet, }; use crate::{EventData, L1Event}; @@ -19,11 +20,10 @@ use crate::{EventData, L1Event}; // Note: don't move as method for L1Event, we don't want to expose alloy's inner type Log to our // base layer's API. pub fn parse_event(log: Log, block_timestamp: BlockTimestamp) -> EthereumBaseLayerResult { - let validate = true; let l1_tx_hash = log.transaction_hash; let log = log.inner; - let event = Starknet::StarknetEvents::decode_log(&log, validate)?.data; + let event = Starknet::StarknetEvents::decode_log(&log)?.data; match event { Starknet::StarknetEvents::LogMessageToL2(event) => { let fee = Fee(event.fee.try_into().map_err(EthereumBaseLayerError::FeeOutOfRange)?); diff --git a/crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs b/crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs index d17a357f19d..4d1ec515563 100644 --- a/crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs +++ b/crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs @@ -5,7 +5,8 @@ use std::time::Duration; use alloy::dyn_abi::SolType; use alloy::eips::eip7840; -use alloy::primitives::Address as EthereumContractAddress; +use alloy::network::Ethereum; +use alloy::primitives::Address; use alloy::providers::{Provider, ProviderBuilder, RootProvider}; use alloy::rpc::json_rpc::RpcError; use alloy::rpc::types::eth::Filter as EthEventFilter; @@ -36,9 +37,20 @@ use crate::{ }; pub type EthereumBaseLayerResult = Result; +pub type EthereumContractAddress = Address; // Wraps the Starknet contract with a type that implements its interface, and is aware of its // events. + +#[cfg(any(test, feature = "testing"))] +// Mocked Starknet contract for testing (no governance). +sol!( + #[sol(rpc)] + Starknet, + "resources/StarknetForSequencerTesting.json" +); +#[cfg(not(any(test, feature = "testing")))] +// Real Starknet contract for production. sol!( #[sol(rpc)] Starknet, @@ -47,51 +59,7 @@ sol!( /// An interface that plays the role of the starknet L1 contract. It is able to create messages to /// L2 from this contract, which appear on the corresponding base layer. -pub type StarknetL1Contract = Starknet::StarknetInstance<(), RootProvider>; - -#[cfg(any(feature = "testing", test))] -pub struct L1ToL2MessageArgs { - pub tx: starknet_api::transaction::L1HandlerTransaction, - pub l1_tx_nonce: u64, -} - -#[cfg(any(feature = "testing", test))] -impl StarknetL1Contract { - /// Converts a given [L1 handler transaction](starknet_api::transaction::L1HandlerTransaction) - /// to match the interface of the given [starknet l1 contract](StarknetL1Contract), and - /// triggers the L1 entry point which sends the message to L2. - pub async fn send_message_to_l2( - &self, - l1_to_l2_message_args: &L1ToL2MessageArgs, - ) -> alloy::rpc::types::TransactionReceipt { - use alloy::primitives::U256; - const PAID_FEE_ON_L1: U256 = U256::from_be_slice(b"paid"); // Arbitrary value. - - let L1ToL2MessageArgs { tx: l1_handler, l1_tx_nonce } = l1_to_l2_message_args; - tracing::info!("Sending message to L2 with the l1 nonce: {l1_tx_nonce}"); - let l2_contract_address = - l1_handler.contract_address.0.key().to_hex_string().parse().unwrap(); - let l2_entry_point = l1_handler.entry_point_selector.0.to_hex_string().parse().unwrap(); - - // The calldata of an L1 handler transaction consists of the L1 sender address followed by - // the transaction payload. We remove the sender address to extract the message - // payload. - let payload = - l1_handler.calldata.0[1..].iter().map(|x| x.to_hex_string().parse().unwrap()).collect(); - let msg = self.sendMessageToL2(l2_contract_address, l2_entry_point, payload); - - msg - // Sets a non-zero fee to be paid on L1. - .value(PAID_FEE_ON_L1) - // Sets the nonce of the L1 handler transaction, to avoid L1 nonce collisions. - .nonce(*l1_tx_nonce) - // Sends the transaction to the Starknet L1 contract. For debugging purposes, replace - // `.send()` with `.call_raw()` to retrieve detailed error messages from L1. - .send().await.expect("Transaction submission to Starknet L1 contract failed.") - // Waits until the transaction is received on L1 and then fetches its receipt. - .get_receipt().await.expect("Transaction was not received on L1 or receipt retrieval failed.") - } -} +pub type StarknetL1Contract = Starknet::StarknetInstance; #[derive(Clone, Debug)] pub struct EthereumBaseLayerContract { @@ -111,6 +79,7 @@ impl EthereumBaseLayerContract { impl BaseLayerContract for EthereumBaseLayerContract { type Error = EthereumBaseLayerError; + /// Get the Starknet block that is proved on the base layer at a specific L1 block number. #[instrument(skip(self), err)] async fn get_proved_block_at( &self, @@ -125,10 +94,9 @@ impl BaseLayerContract for EthereumBaseLayerContract { call_state_block_hash.call_raw().into_future() )?; - let validate = true; - let block_number = sol_data::Uint::<64>::abi_decode(&state_block_number, validate) + let block_number = sol_data::Uint::<64>::abi_decode(&state_block_number) .inspect_err(|err| error!("{err}: {state_block_number}"))?; - let block_hash = sol_data::FixedBytes::<32>::abi_decode(&state_block_hash, validate) + let block_hash = sol_data::FixedBytes::<32>::abi_decode(&state_block_hash) .inspect_err(|err| error!("{err}: {state_block_hash}"))?; Ok(BlockHashAndNumber { number: BlockNumber(block_number), @@ -143,9 +111,7 @@ impl BaseLayerContract for EthereumBaseLayerContract { &self, finality: u64, ) -> EthereumBaseLayerResult> { - let Some(ethereum_block_number) = self.latest_l1_block_number(finality).await? else { - return Ok(None); - }; + let ethereum_block_number = self.latest_l1_block_number(finality).await?; self.get_proved_block_at(ethereum_block_number).await.map(Some) } @@ -159,16 +125,14 @@ impl BaseLayerContract for EthereumBaseLayerContract { .select(block_range.clone()) .events(events) .address(self.config.starknet_contract_address); - let matching_logs = tokio::time::timeout( self.config.timeout_millis, self.contract.provider().get_logs(&filter), ) .await??; - // Debugging. let hashes: Vec<_> = matching_logs.iter().filter_map(|log| log.transaction_hash).collect(); - debug!("Got events in {:?}, transaction hashes: {:?}", block_range, hashes); + debug!("Got events in {:?}, L1 tx hashes: {:?}", block_range, hashes); let block_header_futures = matching_logs.into_iter().map(|log| { let block_number = log.block_number.unwrap(); @@ -185,13 +149,19 @@ impl BaseLayerContract for EthereumBaseLayerContract { async fn latest_l1_block_number( &self, finality: u64, - ) -> EthereumBaseLayerResult> { + ) -> EthereumBaseLayerResult { let block_number = tokio::time::timeout( self.config.timeout_millis, self.contract.provider().get_block_number(), ) .await??; - Ok(block_number.checked_sub(finality)) + let Some(block_number) = block_number.checked_sub(finality) else { + return Err(EthereumBaseLayerError::LatestBlockNumberReturnedTooLow( + block_number, + finality, + )); + }; + Ok(block_number) } #[instrument(skip(self), err)] @@ -199,11 +169,8 @@ impl BaseLayerContract for EthereumBaseLayerContract { &self, finality: u64, ) -> EthereumBaseLayerResult> { - let Some(block_number) = self.latest_l1_block_number(finality).await? else { - return Ok(None); - }; - - self.l1_block_at(block_number).await + let block_number = self.latest_l1_block_number(finality).await?; + Ok(self.l1_block_at(block_number).await?) } #[instrument(skip(self), err)] @@ -289,6 +256,8 @@ pub enum EthereumBaseLayerError { TypeError(#[from] alloy::sol_types::Error), #[error("{0:?}")] UnhandledL1Event(alloy::primitives::Log), + #[error("Block number is too low: {0}, finality: {1}")] + LatestBlockNumberReturnedTooLow(u64, u64), } impl PartialEq for EthereumBaseLayerError { @@ -357,7 +326,7 @@ fn build_contract_instance( starknet_contract_address: EthereumContractAddress, node_url: Url, ) -> StarknetL1Contract { - let l1_client = ProviderBuilder::default().on_http(node_url); + let l1_client = ProviderBuilder::default().connect_http(node_url); // This type is generated from `sol!` macro, and the `new` method assumes it is already // deployed at L1, and wraps it with a type. Starknet::new(starknet_contract_address, l1_client) diff --git a/crates/papyrus_base_layer/src/lib.rs b/crates/papyrus_base_layer/src/lib.rs index ee849f05a09..c5c13560f7c 100644 --- a/crates/papyrus_base_layer/src/lib.rs +++ b/crates/papyrus_base_layer/src/lib.rs @@ -66,10 +66,7 @@ pub trait BaseLayerContract { finality: u64, ) -> Result, Self::Error>; - async fn latest_l1_block_number( - &self, - finality: u64, - ) -> Result, Self::Error>; + async fn latest_l1_block_number(&self, finality: u64) -> Result; async fn latest_l1_block(&self, finality: u64) -> Result, Self::Error>; diff --git a/crates/papyrus_base_layer/src/monitored_base_layer.rs b/crates/papyrus_base_layer/src/monitored_base_layer.rs index 31eb10782d1..d4f47dea7e6 100644 --- a/crates/papyrus_base_layer/src/monitored_base_layer.rs +++ b/crates/papyrus_base_layer/src/monitored_base_layer.rs @@ -52,13 +52,17 @@ impl MonitoredBaseLayer { /// of an external HTTP call. async fn ensure_operational(&self) -> Result<(), MonitoredBaseLayerError> { let active_l1_endpoint = self.monitor.get_active_l1_endpoint().await; + let current_node_url; + { + current_node_url = self.current_node_url.read().await.clone(); + } // Drop the read lock match active_l1_endpoint { - Ok(new_node_url) if new_node_url != *self.current_node_url.read().await => { + Ok(new_node_url) if new_node_url != current_node_url => { info!( "L1 endpoint {} is no longer operational, switching to new operational L1 \ endpoint: {}", - self.current_node_url.read().await, - &new_node_url + to_safe_string(¤t_node_url), + to_safe_string(&new_node_url) ); let mut base_layer = self.base_layer.lock().await; @@ -107,10 +111,7 @@ impl BaseLayerContract for MonitoredBaseLaye .map_err(|err| MonitoredBaseLayerError::BaseLayerContractError(err)) } - async fn latest_l1_block_number( - &self, - finality: u64, - ) -> Result, Self::Error> { + async fn latest_l1_block_number(&self, finality: u64) -> Result { self.get() .await? .latest_l1_block_number(finality) @@ -234,3 +235,10 @@ impl std::fmt::Debug for MonitoredBaseLayerE } } } + +// TODO(guyn): this is duplicated code from apollo_l1_endpoint_monitor/src/monitor.rs +// TODO(guyn): when it is moved to apollo_infra_utils, we should import it instead. +fn to_safe_string(url: &Url) -> String { + // We print only the hostnames to avoid leaking the API keys. + url.host().map_or_else(|| "no host in url!".to_string(), |host| host.to_string()) +} diff --git a/crates/papyrus_base_layer/src/test_utils.rs b/crates/papyrus_base_layer/src/test_utils.rs index a4c92ea5ecc..771e70eb280 100644 --- a/crates/papyrus_base_layer/src/test_utils.rs +++ b/crates/papyrus_base_layer/src/test_utils.rs @@ -1,34 +1,17 @@ -use std::fs::File; -use std::process::Command; - use alloy::network::TransactionBuilder; -use alloy::node_bindings::{Anvil, AnvilInstance, NodeError as AnvilError}; -pub(crate) use alloy::primitives::Address as EthereumContractAddress; -use alloy::primitives::{Address, U256}; +use alloy::primitives::{address as ethereum_address, U256}; use alloy::providers::Provider; use alloy::rpc::types::TransactionRequest; -use colored::*; -use ethers::utils::{Ganache, GanacheInstance}; use starknet_api::hash::StarkHash; -use tar::Archive; -use tempfile::{tempdir, TempDir}; use tracing::debug; use url::Url; use crate::ethereum_base_layer_contract::{ EthereumBaseLayerConfig, EthereumBaseLayerContract, - Starknet, - StarknetL1Contract, + EthereumContractAddress, }; -type TestEthereumNodeHandle = (GanacheInstance, TempDir); - -const MINIMAL_GANACHE_VERSION: u8 = 7; - -// See Anvil documentation: -// https://docs.rs/ethers-core/latest/ethers_core/utils/struct.Anvil.html#method.new. -const DEFAULT_ANVIL_PORT: u16 = 8545; // This address is commonly used as the L1 address of the Starknet core contract. // TODO(Arni): Replace with constant with use of `AnvilInstance::address(&self)`. pub const DEFAULT_ANVIL_L1_DEPLOYED_ADDRESS: &str = "0x5fbdb2315678afecb367f032d93f642f64180aa3"; @@ -38,129 +21,21 @@ pub const DEFAULT_ANVIL_L1_DEPLOYED_ADDRESS: &str = "0x5fbdb2315678afecb367f032d // Given an `AnvilInstance`, this address can be retrieved by calling `anvil.addresses()[0]`. pub const DEFAULT_ANVIL_L1_ACCOUNT_ADDRESS: StarkHash = StarkHash::from_hex_unchecked("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); -pub const DEFAULT_ANVIL_ADDITIONAL_ADDRESS_INDEX: usize = 3; - -// *** -// DEPRECATED: Use the anvil constructor, this constructor is deprecated as it uses ganache. -// *** -// -// Returns a Ganache instance, preset with a Starknet core contract and some state updates: -// Starknet contract address: 0xe2aF2c1AE11fE13aFDb7598D0836398108a4db0A -// Ethereum block number starknet block number starknet block hash -// 10 100 0x100 -// 20 200 0x200 -// 30 300 0x300 -// The blockchain is at Ethereum block number 31. -// Note: Requires Ganache@7.4.3 installed. -// TODO(Gilad): `Ganache` and `ethers` have both been deprecated. Also, `ethers`s' replacement, -// `alloy`, no longer supports `Ganache`. Once we fully support anvil, remove this util, `ganache` -// and `ethers``. -#[deprecated(note = "Ganache is dead and will be removed soon, use Anvil instead, unless you \ - need something we don't support on anvil yet.")] -pub fn get_test_ethereum_node() -> (TestEthereumNodeHandle, EthereumContractAddress) { - const SN_CONTRACT_ADDR: &str = "0xe2aF2c1AE11fE13aFDb7598D0836398108a4db0A"; - // Verify correct Ganache version. - let ganache_version = String::from_utf8_lossy( - &Command::new("ganache") - .arg("--version") - .output() - .expect("Failed to get Ganache version, check if it is installed.") - .stdout, - ) - .to_string(); - const GANACHE_VERSION_PREFIX: &str = "ganache v"; - let ganache_version = ganache_version - .strip_prefix(GANACHE_VERSION_PREFIX) - .expect("Failed to parse Ganache version."); - let major_version = ganache_version - .split('.') - .next() - .expect("Failed to parse Ganache major version.") - .parse::() - .expect("Failed to parse Ganache major version."); - assert!( - major_version >= MINIMAL_GANACHE_VERSION, - "Wrong Ganache version, expecting at least version 7. To install, run `npm install -g \ - ganache`." - ); - const DB_NAME: &str = "ganache-db"; - let db_archive_path = format!("resources/{DB_NAME}.tar"); - - // Unpack the Ganache db tar file into a temporary dir. - let mut archive = Archive::new(File::open(db_archive_path).expect("Ganache db not found.")); - let ganache_db = tempdir().unwrap(); - archive.unpack(ganache_db.path()).unwrap(); - - // Start Ganache instance. This will panic if Ganache is not installed. - let db_path = ganache_db.path().join(DB_NAME); - let ganache = Ganache::new().args(["--db", db_path.to_str().unwrap()]).spawn(); - - ((ganache, ganache_db), SN_CONTRACT_ADDR.to_string().parse().unwrap()) -} - -// TODO(Arni): Make port non-optional. -// Spin up Anvil instance, a local Ethereum node, dies when dropped. -pub fn anvil(port: Option) -> AnvilInstance { - let mut anvil = Anvil::new(); - // If the port is not set explicitly, a random ephemeral port is bound and used. - if let Some(port) = port { - anvil = anvil.port(port); - } - - anvil.try_spawn().unwrap_or_else(|error| match error { - AnvilError::SpawnError(e) if e.to_string().contains("No such file or directory") => { - panic!( - "\n{}\n{}\n", - "Anvil binary not found!".bold().red(), - "Install instructions (for local development):\n - cargo install --git \ - https://github.com/foundry-rs/foundry anvil --locked --tag=v0.3.0" - .yellow() - ) - } - _ => panic!("Failed to spawn Anvil: {}", error.to_string().red()), - }) -} - -pub fn ethereum_base_layer_config_for_anvil(port: Option) -> (EthereumBaseLayerConfig, Url) { - // Use the specified port if provided; otherwise, default to Anvil's default port. - let non_optional_port = port.unwrap_or(DEFAULT_ANVIL_PORT); - let endpoint = format!("http://localhost:{non_optional_port}"); - let url = Url::parse(&endpoint).unwrap(); - let config = EthereumBaseLayerConfig { - starknet_contract_address: DEFAULT_ANVIL_L1_DEPLOYED_ADDRESS.parse().unwrap(), - ..Default::default() - }; - (config, url) -} - -pub fn anvil_instance_from_url(url: &Url) -> AnvilInstance { - let port = url.port(); - let anvil = anvil(port); - assert_eq!(url, &anvil.endpoint_url(), "Unexpected config for Anvil instance."); - anvil -} - -pub async fn spawn_anvil_and_deploy_starknet_l1_contract( - config: &EthereumBaseLayerConfig, - url: &Url, -) -> (AnvilInstance, StarknetL1Contract) { - let anvil = anvil_instance_from_url(url); - let starknet_l1_contract = deploy_starknet_l1_contract(config.clone(), url).await; - (anvil, starknet_l1_contract) -} - -pub async fn deploy_starknet_l1_contract( - config: EthereumBaseLayerConfig, - url: &Url, -) -> StarknetL1Contract { - let ethereum_base_layer_contract = EthereumBaseLayerContract::new(config, url.clone()); - Starknet::deploy(ethereum_base_layer_contract.contract.provider().clone()).await.unwrap() -} - +/// One of the 10 pre-funded Anvil preloaded accounts. Retrieved by calling `anvil.addresses()[3]`. +// TODO(Gilad): consider moving into anvil base layer. +pub const ARBITRARY_ANVIL_L1_ACCOUNT_ADDRESS: EthereumContractAddress = + ethereum_address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906"); +/// One of the 10 pre-funded Anvil preloaded accounts. Retrieved by calling `anvil.addresses()[4]`. +// TODO(Gilad): consider moving into anvil base layer. +pub const OTHER_ARBITRARY_ANVIL_L1_ACCOUNT_ADDRESS: EthereumContractAddress = + ethereum_address!("0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65"); + +// FIXME: This should be part of AnvilBaseLayer, however the usage in the simulator doesn't allow +// that, since it is coupled with a manual invocation of an anvil instance that is managed inside +// the github workflow. pub async fn make_block_history_on_anvil( - sender_address: Address, - receiver_address: Address, + sender_address: EthereumContractAddress, + receiver_address: EthereumContractAddress, base_layer_config: EthereumBaseLayerConfig, url: &Url, num_blocks: usize, @@ -192,3 +67,27 @@ pub async fn make_block_history_on_anvil( prev_block_number = new_block_number; } } + +/// Mine multiple blocks instantly on Anvil using the `anvil_mine` RPC method. +/// +/// Note: This creates empty blocks. For blocks with transactions, use the +/// `make_block_history_on_anvil` function instead. +pub async fn anvil_mine_blocks( + base_layer_config: EthereumBaseLayerConfig, + num_blocks: u64, + url: &Url, +) { + let base_layer = EthereumBaseLayerContract::new(base_layer_config.clone(), url.clone()); + let provider = base_layer.contract.provider(); + + let block_before = provider.get_block_number().await.expect("Failed to get block number"); + debug!("Block number before mining: {}", block_before); + + let _result: Option = provider + .raw_request("anvil_mine".into(), [num_blocks]) + .await + .expect("Failed to mine blocks on Anvil"); + + let block_after = provider.get_block_number().await.expect("Failed to get block number"); + debug!("Block number after mining: {}", block_after); +} diff --git a/crates/papyrus_node/src/config/snapshots/papyrus_node__config__config_test__dump_default_config.snap b/crates/papyrus_node/src/config/snapshots/papyrus_node__config__config_test__dump_default_config.snap index 04d5d43fdc3..82184c69be9 100644 --- a/crates/papyrus_node/src/config/snapshots/papyrus_node__config__config_test__dump_default_config.snap +++ b/crates/papyrus_node/src/config/snapshots/papyrus_node__config__config_test__dump_default_config.snap @@ -101,6 +101,34 @@ expression: dumped_default_config "value": true, "privacy": "TemporaryValue" }, + "consensus.dynamic_config.sync_retry_interval": { + "description": "The duration (seconds) between sync attempts.", + "value": { + "$serde_json::private::Number": "1.0" + }, + "privacy": "Public" + }, + "consensus.dynamic_config.timeouts.precommit_timeout": { + "description": "The timeout (seconds) for a precommit.", + "value": { + "$serde_json::private::Number": "1.0" + }, + "privacy": "Public" + }, + "consensus.dynamic_config.timeouts.prevote_timeout": { + "description": "The timeout (seconds) for a prevote.", + "value": { + "$serde_json::private::Number": "1.0" + }, + "privacy": "Public" + }, + "consensus.dynamic_config.timeouts.proposal_timeout": { + "description": "The timeout (seconds) for a proposal.", + "value": { + "$serde_json::private::Number": "3.0" + }, + "privacy": "Public" + }, "consensus.dynamic_config.validator_id": { "description": "The validator id of the node.", "value": "0x64", @@ -134,34 +162,6 @@ expression: dumped_default_config }, "privacy": "Public" }, - "consensus.static_config.sync_retry_interval": { - "description": "The duration (seconds) between sync attempts.", - "value": { - "$serde_json::private::Number": "1.0" - }, - "privacy": "Public" - }, - "consensus.static_config.timeouts.precommit_timeout": { - "description": "The timeout (seconds) for a precommit.", - "value": { - "$serde_json::private::Number": "1.0" - }, - "privacy": "Public" - }, - "consensus.static_config.timeouts.prevote_timeout": { - "description": "The timeout (seconds) for a prevote.", - "value": { - "$serde_json::private::Number": "1.0" - }, - "privacy": "Public" - }, - "consensus.static_config.timeouts.proposal_timeout": { - "description": "The timeout (seconds) for a proposal.", - "value": { - "$serde_json::private::Number": "3.0" - }, - "privacy": "Public" - }, "context.#is_none": { "description": "Flag for an optional field.", "value": true, @@ -191,11 +191,6 @@ expression: dumped_default_config "value": "SN_MAIN", "privacy": "Public" }, - "context.constant_l2_gas_price": { - "description": "If true, sets STRK gas price to its minimum price from the versioned constants.", - "value": false, - "privacy": "Public" - }, "context.l1_da_mode": { "description": "The data availability mode, true: Blob, false: Calldata.", "value": true, @@ -250,6 +245,54 @@ expression: dumped_default_config }, "privacy": "Public" }, + "context.override_eth_to_fri_rate": { + "description": "Replace the Eth-to-Fri conversion rate with this value.", + "value": { + "$serde_json::private::Number": "0" + }, + "privacy": "Public" + }, + "context.override_eth_to_fri_rate.#is_none": { + "description": "Flag for an optional field.", + "value": true, + "privacy": "TemporaryValue" + }, + "context.override_l1_data_gas_price_wei": { + "description": "Replace the L1 data gas price (wei) with this value.", + "value": { + "$serde_json::private::Number": "0" + }, + "privacy": "Public" + }, + "context.override_l1_data_gas_price_wei.#is_none": { + "description": "Flag for an optional field.", + "value": true, + "privacy": "TemporaryValue" + }, + "context.override_l1_gas_price_wei": { + "description": "Replace the L1 gas price (wei) with this value.", + "value": { + "$serde_json::private::Number": "0" + }, + "privacy": "Public" + }, + "context.override_l1_gas_price_wei.#is_none": { + "description": "Flag for an optional field.", + "value": true, + "privacy": "TemporaryValue" + }, + "context.override_l2_gas_price_fri": { + "description": "Replace the L2 gas price (fri) with this value.", + "value": { + "$serde_json::private::Number": "0" + }, + "privacy": "Public" + }, + "context.override_l2_gas_price_fri.#is_none": { + "description": "Flag for an optional field.", + "value": true, + "privacy": "TemporaryValue" + }, "context.proposal_buffer_size": { "description": "The buffer size for streaming outbound proposals.", "value": { @@ -264,6 +307,16 @@ expression: dumped_default_config }, "privacy": "Public" }, + "context.validator_ids": { + "description": "Optional explicit set of validator IDs (comma separated).", + "value": "", + "privacy": "Public" + }, + "context.validator_ids.#is_none": { + "description": "Flag for an optional field.", + "value": true, + "privacy": "TemporaryValue" + }, "monitoring_gateway.collect_metrics": { "description": "If true, collect and return metrics in the monitoring gateway.", "value": false, diff --git a/crates/starknet_api/src/compression_utils.rs b/crates/starknet_api/src/compression_utils.rs index 43b126d6cbf..5bdba77b125 100644 --- a/crates/starknet_api/src/compression_utils.rs +++ b/crates/starknet_api/src/compression_utils.rs @@ -14,6 +14,8 @@ pub enum CompressionError { Serde(#[from] serde_json::Error), #[error(transparent)] Decode(#[from] base64::DecodeError), + #[error("Decompressed data exceeds maximum size limit of {limit} bytes")] + SizeLimitExceeded { limit: usize }, } /// Compress the value using gzip with the default compression level and encode it in base64. @@ -27,11 +29,29 @@ where Ok(base64::encode(compressed_data)) } -/// Decompress the value from base64 and gzip. -pub fn decode_and_decompress(value: &str) -> Result { +/// Decompresses the provided data with size limits. +fn decompress_with_size_limit( + decoded_data: Vec, + max_size: usize, +) -> Result, CompressionError> { + let decompressor = flate2::read::GzDecoder::new(&decoded_data[..]); + let mut decompressed_data = Vec::new(); + decompressor + .take((max_size + 1).try_into().expect("max_size should be less than usize::MAX")) + .read_to_end(&mut decompressed_data)?; + if decompressed_data.len() > max_size { + return Err(CompressionError::SizeLimitExceeded { limit: max_size }); + } + Ok(decompressed_data) +} + +/// Decodes the provided data with size limits. +// TODO(dan): consider limiting the time it takes to decompress. +pub fn decode_and_decompress_with_size_limit( + value: &str, + max_size: usize, +) -> Result { let decoded_data = base64::decode(value)?; - let mut decompressor = flate2::read::GzDecoder::new(&decoded_data[..]); - let mut decompressed_data = String::new(); - decompressor.read_to_string(&mut decompressed_data)?; - Ok(serde_json::from_str(&decompressed_data)?) + let decompressed_data = decompress_with_size_limit(decoded_data, max_size)?; + Ok(serde_json::from_reader(decompressed_data.as_slice())?) } diff --git a/crates/starknet_api/src/compression_utils_test.rs b/crates/starknet_api/src/compression_utils_test.rs index 01b4434996c..24b4324434a 100644 --- a/crates/starknet_api/src/compression_utils_test.rs +++ b/crates/starknet_api/src/compression_utils_test.rs @@ -1,7 +1,12 @@ +use assert_matches::assert_matches; use pretty_assertions::assert_eq; use starknet_crypto::Felt; -use crate::compression_utils::{compress_and_encode, decode_and_decompress}; +use crate::compression_utils::{ + compress_and_encode, + decode_and_decompress_with_size_limit, + CompressionError, +}; use crate::test_utils::read_json_file; #[test] @@ -16,6 +21,26 @@ fn compress_and_encode_hardcoded_value() { fn decode_and_decompress_hardcoded_value() { let sierra_program_base64: String = read_json_file("sierra_program_base64.json"); let expected_value: Vec = read_json_file("sierra_program.json"); - let value: Vec = decode_and_decompress(&sierra_program_base64).unwrap(); + let serialized_size_of_expected_value = serde_json::to_string(&expected_value).unwrap().len(); + let value: Vec = decode_and_decompress_with_size_limit( + &sierra_program_base64, + serialized_size_of_expected_value, + ) + .unwrap(); assert_eq!(value, expected_value); } + +#[test] +fn decode_and_decompress_hardcoded_value_with_size_limit_exceeded() { + let sierra_program_base64: String = read_json_file("sierra_program_base64.json"); + let expected_value: Vec = read_json_file("sierra_program.json"); + let serialized_size_of_expected_value = serde_json::to_string(&expected_value).unwrap().len(); + let result: Result, CompressionError> = decode_and_decompress_with_size_limit( + &sierra_program_base64, + serialized_size_of_expected_value - 1, + ); + assert_matches!( + result, + Err(CompressionError::SizeLimitExceeded { limit: expected_limit }) if expected_limit == serialized_size_of_expected_value - 1 + ); +} diff --git a/crates/starknet_api/src/core.rs b/crates/starknet_api/src/core.rs index 79cd461d423..9e216775067 100644 --- a/crates/starknet_api/src/core.rs +++ b/crates/starknet_api/src/core.rs @@ -3,6 +3,7 @@ mod core_test; use std::fmt::Debug; +use std::str::FromStr; use std::sync::LazyLock; use num_traits::ToPrimitive; @@ -154,6 +155,15 @@ impl From for ContractAddress { impl_from_through_intermediate!(u128, ContractAddress, u8, u16, u32, u64); +impl FromStr for ContractAddress { + type Err = StarknetApiError; + fn from_str(s: &str) -> Result { + let felt = Felt::from_str(s) + .map_err(|e| StarknetApiError::OutOfRange { string: format!("{e}") })?; + Ok(ContractAddress(PatriciaKey::try_from(felt)?)) + } +} + /// The maximal size of storage var. pub const MAX_STORAGE_ITEM_SIZE: u16 = 256; /// The prefix used in the calculation of a contract address. diff --git a/crates/starknet_api/src/executable_transaction.rs b/crates/starknet_api/src/executable_transaction.rs index 2fe599a98da..dc9c7e4180a 100644 --- a/crates/starknet_api/src/executable_transaction.rs +++ b/crates/starknet_api/src/executable_transaction.rs @@ -30,7 +30,7 @@ use crate::transaction::{ TransactionHasher, TransactionVersion, }; -use crate::StarknetApiError; +use crate::{CasmHashMismatch, StarknetApiError}; macro_rules! implement_inner_tx_getter_calls { ($(($field:ident, $field_type:ty)),*) => { @@ -203,9 +203,7 @@ impl DeclareTransaction { /// Verifies that the compiled class hash field in the declare tx, /// is compiled_class_hash_v2 of the compiled contract. - pub fn check_compile_class_hash_v2_declaration( - &self, - ) -> Result<(), (ClassHash, CompiledClassHash, CompiledClassHash)> { + pub fn check_compile_class_hash_v2_declaration(&self) -> Result<(), StarknetApiError> { let compiled_class = &self.class_info.contract_class; let compiled_class_hash_v2 = match &compiled_class { ContractClass::V0(_) => return Ok(()), @@ -213,10 +211,14 @@ impl DeclareTransaction { }; let compiled_class_hash = self.compiled_class_hash(); if compiled_class_hash_v2 != compiled_class_hash { - Err((self.class_hash(), compiled_class_hash, compiled_class_hash_v2)) - } else { - Ok(()) + let err_var = CasmHashMismatch { + hash: self.class_hash(), + actual: compiled_class_hash, + expected: compiled_class_hash_v2, + }; + return Err(StarknetApiError::DeclareTransactionCasmHashMissMatch(Box::new(err_var))); } + Ok(()) } // Returns whether the declare transaction is for bootstrapping. diff --git a/crates/starknet_api/src/lib.rs b/crates/starknet_api/src/lib.rs index 1b731f18bdb..5278a727e80 100644 --- a/crates/starknet_api/src/lib.rs +++ b/crates/starknet_api/src/lib.rs @@ -30,8 +30,27 @@ pub mod versioned_constants_logic; use std::num::ParseIntError; +use crate::core::{ClassHash, CompiledClassHash}; use crate::transaction::TransactionVersion; +#[derive(Clone, Debug, PartialEq)] +pub struct CasmHashMismatch { + hash: ClassHash, + actual: CompiledClassHash, + expected: CompiledClassHash, +} + +impl std::fmt::Display for CasmHashMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Mismatch compiled class hash for class with hash {:#064x}. Actual: {:#064x}, \ + Expected: {:#064x}", + self.hash.0, self.actual.0, self.expected.0 + ) + } +} + /// The error type returned by StarknetApi. // Note: if you need `Eq` see InnerDeserializationError's docstring. #[derive(thiserror::Error, Clone, Debug, PartialEq)] @@ -73,6 +92,11 @@ pub enum StarknetApiError { ParseSierraVersionError(String), #[error("Unsupported transaction type: {0}")] UnknownTransactionType(String), + #[error( + "Mismatch compiled class hash for class with hash {:#064x}. Actual: {:#064x}, Expected: {:#064x}", + .0.hash.0, .0.actual.0, .0.expected.0 + )] + DeclareTransactionCasmHashMissMatch(Box), } pub type StarknetApiResult = Result; diff --git a/crates/starknet_committer_and_os_cli/benches/bench_split_and_prepare_post.sh b/crates/starknet_committer_and_os_cli/benches/bench_split_and_prepare_post.sh index c766b72f668..caf4e54ee48 100644 --- a/crates/starknet_committer_and_os_cli/benches/bench_split_and_prepare_post.sh +++ b/crates/starknet_committer_and_os_cli/benches/bench_split_and_prepare_post.sh @@ -1,29 +1,80 @@ #!/bin/env bash +# +# This script runs benchmarks, compares them to the baseline, and prepares results for posting. +# It will mark the CI to fail if any benchmark regresses by more than the threshold. +# +# Usage: bench_split_and_prepare_post.sh +# benchmarks_list: File containing list of benchmark names (one per line) +# benchmark_results: Output file for formatted results (used for PR comments) set -e benchmarks_list=${1} benchmark_results=${2} -# Benchmark the new code, splitting the benchmarks +# 8% threshold for acceptable regression. +threshold=8.0 + +# ============================================================================ +# Step 1: Run benchmarks. +# ============================================================================ +# Run each benchmark individually and save output to separate files. # TODO(Aner): split the output file instead. cat ${benchmarks_list} | while read line; do cargo bench -p starknet_committer_and_os_cli $line > ${line}.txt; + # Keep only the benchmark output, remove everything before the benchmark name. sed -i '/'"${line}"'/,$!d' ${line}.txt; done -# Prepare the results for posting comment. +# ============================================================================ +# Step 2: Analyze results and check thresholds +# ============================================================================ echo "Benchmark movements: " > ${benchmark_results} +has_major_regression=false + cat ${benchmarks_list} | while read line; do + # Parse the percentage change and check against threshold. + # Find the line that contains the percent change summary printed by Criterion. + # Typical format (note the leading spaces): + # " change: [-2.3% -1.2% +0.1%]" + # Semantics: [lower_bound point_estimate upper_bound] + change_line=$(grep "change:" ${line}.txt || true) + if [ -n "$change_line" ]; then + # Steps to extract the point estimate (the SECOND number inside the brackets): + # 1) Split the line by whitespace and take field 3 using awk + # Fields: $1="change:" $2="[-2.3%" $3="-1.2%" $4="+0.1%]" + # 2) Strip '%' and any leading '+' so it's a clean float for bc + # Examples: "+6.4%" -> "6.4"; "-2.0%" -> "-2.0" + change_pct=$(echo "$change_line" | awk '{print $3}' | tr -d '%+') + if [ -n "$change_pct" ]; then + # fail if point estimate > threshold. + if awk -v x="$change_pct" -v y="$threshold" 'BEGIN { exit(!(x > y)) }'; then + if [ "$has_major_regression" = false ]; then + echo "" >> ${benchmark_results} + echo "---" >> ${benchmark_results} + echo "❌ **CI WILL FAIL: Benchmarks exceeded ${threshold}% regression threshold**" >> ${benchmark_results} + echo "" >> ${benchmark_results} + has_major_regression=true + fi + echo "ERROR: ${line} regressed by ${change_pct}%, exceeding ${threshold}% threshold!" >> ${benchmark_results} + fi + fi + fi + + # Check if this benchmark regressed. if grep -q "regressed" ${line}.txt; then echo "**${line} performance regressed!**" >> ${benchmark_results}; cat ${line}.txt >> ${benchmark_results}; + + # Check if this benchmark improved. elif grep -q "improved" ${line}.txt; then echo "_${line} performance improved_ :smiley_cat:" >> ${benchmark_results}; cat ${line}.txt >> ${benchmark_results}; fi; done + +# If no significant changes were detected. if ! (grep -q "regressed" ${benchmark_results} || grep -q "improved" ${benchmark_results}); then echo "No major performance changes detected." >> ${benchmark_results}; fi diff --git a/crates/starknet_committer_and_os_cli/src/kzg_cli.rs b/crates/starknet_committer_and_os_cli/src/kzg_cli.rs new file mode 100644 index 00000000000..30525d0e6d7 --- /dev/null +++ b/crates/starknet_committer_and_os_cli/src/kzg_cli.rs @@ -0,0 +1 @@ +pub mod run_kzg_cli; diff --git a/crates/starknet_committer_and_os_cli/src/kzg_cli/run_kzg_cli.rs b/crates/starknet_committer_and_os_cli/src/kzg_cli/run_kzg_cli.rs new file mode 100644 index 00000000000..1dfc1013009 --- /dev/null +++ b/crates/starknet_committer_and_os_cli/src/kzg_cli/run_kzg_cli.rs @@ -0,0 +1,47 @@ +use clap::{Parser, Subcommand}; +use starknet_os::hints::hint_implementation::kzg::utils::{ + compute_blob_commitments, + compute_legacy_blob_commitments, +}; +use tracing::info; + +use crate::shared_utils::read::{load_input, write_to_file}; +use crate::shared_utils::types::IoArgs; + +#[derive(Parser, Debug)] +pub struct KzgCliCommand { + #[clap(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + ComputeBlobCommitments { + #[clap(flatten)] + io_args: IoArgs, + }, + ComputeLegacyBlobCommitments { + #[clap(flatten)] + io_args: IoArgs, + }, +} + +pub fn run_kzg_cli(kzg_command: KzgCliCommand) { + info!("Starting KZG CLI with command: \n{:?}", kzg_command); + match kzg_command.command { + Command::ComputeBlobCommitments { io_args: IoArgs { input_path, output_path } } => { + let raw_blobs: Vec> = load_input(input_path); + let blobs = compute_blob_commitments(raw_blobs) + .unwrap_or_else(|error| panic!("Failed to calculate blob commitments: {error}")); + write_to_file(&output_path, &blobs); + } + // TODO(Yoni): remove this command once python migrates to the new blob commitments. + Command::ComputeLegacyBlobCommitments { io_args: IoArgs { input_path, output_path } } => { + let raw_blobs: Vec> = load_input(input_path); + let blobs = compute_legacy_blob_commitments(raw_blobs).unwrap_or_else(|error| { + panic!("Failed to calculate legacy blob commitments: {error}") + }); + write_to_file(&output_path, &blobs); + } + }; +} diff --git a/crates/starknet_committer_and_os_cli/src/lib.rs b/crates/starknet_committer_and_os_cli/src/lib.rs index b1c73f78349..027abffabcd 100644 --- a/crates/starknet_committer_and_os_cli/src/lib.rs +++ b/crates/starknet_committer_and_os_cli/src/lib.rs @@ -1,5 +1,6 @@ pub mod block_hash_cli; pub mod committer_cli; +pub mod kzg_cli; pub mod os_cli; pub mod shared_utils; pub mod tracing_utils; diff --git a/crates/starknet_committer_and_os_cli/src/main.rs b/crates/starknet_committer_and_os_cli/src/main.rs index d43d6cec7fc..de41354bfdc 100644 --- a/crates/starknet_committer_and_os_cli/src/main.rs +++ b/crates/starknet_committer_and_os_cli/src/main.rs @@ -7,6 +7,7 @@ use starknet_committer_and_os_cli::committer_cli::run_committer_cli::{ run_committer_cli, CommitterCliCommand, }; +use starknet_committer_and_os_cli::kzg_cli::run_kzg_cli::{run_kzg_cli, KzgCliCommand}; use starknet_committer_and_os_cli::os_cli::run_os_cli::{run_os_cli, OsCliCommand}; use starknet_committer_and_os_cli::tracing_utils::configure_tracing; use tracing::info; @@ -28,6 +29,8 @@ enum CommitterOrOsCommand { Committer(CommitterCliCommand), /// Run BlockHash CLI. BlockHash(BlockHashCliCommand), + /// Run KZG CLI. + Kzg(KzgCliCommand), /// Run OS CLI. OS(OsCliCommand), } @@ -55,5 +58,8 @@ async fn main() { CommitterOrOsCommand::BlockHash(command) => { run_block_hash_cli(command).await; } + CommitterOrOsCommand::Kzg(command) => { + run_kzg_cli(command); + } } } diff --git a/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs b/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs index 343f5bf960f..eed838a00f9 100644 --- a/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs +++ b/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs @@ -9,6 +9,7 @@ use serde::Serialize; use starknet_os::hint_processor::os_logger::OsTransactionTrace; use starknet_os::hints::enum_definition::AllHints; use starknet_os::metrics::{AggregatorMetrics, OsMetrics, ProgramRunInfo}; +use starknet_os::opcode_instances::OpcodeInstanceCounts; use starknet_types_core::felt::Felt; use tracing::info; use tracing::level_filters::LevelFilter; @@ -135,6 +136,7 @@ pub(crate) struct OsCliMetrics { pub deprecated_syscall_usages: Vec, pub run_info: OsCliRunInfo, pub execution_resources: ExecutionResources, + pub opcode_instances: OpcodeInstanceCounts, } impl From for OsCliMetrics { @@ -144,6 +146,7 @@ impl From for OsCliMetrics { deprecated_syscall_usages: metrics.deprecated_syscall_usages, run_info: metrics.run_info.into(), execution_resources: metrics.execution_resources, + opcode_instances: metrics.opcode_instances, } } } diff --git a/crates/starknet_os/Cargo.toml b/crates/starknet_os/Cargo.toml index 486d85b6953..3fbf0a6438d 100644 --- a/crates/starknet_os/Cargo.toml +++ b/crates/starknet_os/Cargo.toml @@ -48,6 +48,7 @@ rand.workspace = true regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } +serde_with.workspace = true sha2.workspace = true sha3.workspace = true shared_execution_objects.workspace = true diff --git a/crates/starknet_os/src/hints/enum_definition.rs b/crates/starknet_os/src/hints/enum_definition.rs index b997a562c40..248a2bb062f 100644 --- a/crates/starknet_os/src/hints/enum_definition.rs +++ b/crates/starknet_os/src/hints/enum_definition.rs @@ -939,7 +939,7 @@ ids.initial_carried_outputs = segments.gen_arg( ( GenerateKeysUsingSha256Hash, calculate_keys_using_sha256_hash, - "generate_keys_from_hash(ids.compressed_start, ids.compressed_dst, ids.n_keys)" + "generate_keys_from_hash(ids.compressed_start, ids.compressed_end, ids.n_keys)" ) ); diff --git a/crates/starknet_os/src/hints/hint_implementation/compiled_class/compiled_class_test.rs b/crates/starknet_os/src/hints/hint_implementation/compiled_class/compiled_class_test.rs index c248e438dda..2847340ffc4 100644 --- a/crates/starknet_os/src/hints/hint_implementation/compiled_class/compiled_class_test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/compiled_class/compiled_class_test.rs @@ -29,6 +29,7 @@ use starknet_types_core::felt::Felt; use crate::hints::hint_implementation::compiled_class::utils::create_bytecode_segment_structure; use crate::hints::vars::Const; +use crate::opcode_instances::{get_opcode_instances, OpcodeInstanceCounts}; use crate::test_utils::cairo_runner::{ initialize_cairo_runner, run_cairo_0_entrypoint, @@ -68,6 +69,7 @@ const EXPECTED_BUILTIN_USAGE_PARTIAL_CONTRACT_V2_HASH: expect_test::Expect = const EXPECTED_N_STEPS_PARTIAL_CONTRACT_V2_HASH: Expect = expect!["41697"]; // Allowed margin between estimated and actual execution resources. const ALLOWED_MARGIN_BLAKE_N_STEPS: usize = 267; +const ALLOWED_MARGIN_BLAKE_OPCODE_COUNT: usize = 4; /// Specifies the expected inputs and outputs for testing a class hash version. /// Includes entrypoint, bytecode, and expected runtime behavior. @@ -88,12 +90,20 @@ trait HashVersionTestSpec { fn expected_hash(&self) -> Expect; /// The allowed margin for the number of steps. fn allowed_margin_n_steps(&self) -> usize; + /// The allowed margin for the number of Blake opcodes. + fn allowed_margin_blake_opcode_count(&self) -> usize; /// Estimates the execution resources for the compiled class hash function. fn estimate_execution_resources( &self, bytecode_segment_felt_sizes: &NestedFeltCounts, entry_points_by_type: &EntryPointsByType, ) -> ExecutionResources; + /// Estimates the number of Blake opcodes used for the compiled class hash function. + fn estimated_blake_opcode_count( + &self, + bytecode_segment_felt_sizes: &NestedFeltCounts, + entry_points_by_type: &EntryPointsByType, + ) -> usize; } impl HashVersionTestSpec for HashVersion { @@ -156,6 +166,12 @@ impl HashVersionTestSpec for HashVersion { HashVersion::V2 => ALLOWED_MARGIN_BLAKE_N_STEPS, } } + fn allowed_margin_blake_opcode_count(&self) -> usize { + match self { + HashVersion::V1 => 0, + HashVersion::V2 => ALLOWED_MARGIN_BLAKE_OPCODE_COUNT, + } + } fn estimate_execution_resources( &self, bytecode_segment_felt_sizes: &NestedFeltCounts, @@ -178,6 +194,22 @@ impl HashVersionTestSpec for HashVersion { } } } + fn estimated_blake_opcode_count( + &self, + bytecode_segment_felt_sizes: &NestedFeltCounts, + entry_points_by_type: &EntryPointsByType, + ) -> usize { + match self { + HashVersion::V1 => 0, + HashVersion::V2 => { + CasmV2HashResourceEstimate::estimated_resources_of_compiled_class_hash( + bytecode_segment_felt_sizes, + entry_points_by_type, + ) + .blake_count() + } + } + } } /// Runs the compiled class hash entry point for the given contract class, @@ -187,11 +219,11 @@ fn run_compiled_class_hash_entry_point( contract_class: &CasmContractClass, load_full_contract: bool, hash_version: &HashVersion, -) -> (ExecutionResources, Felt) { +) -> (ExecutionResources, OpcodeInstanceCounts, Felt) { // Set up the entry point runner configuration. let runner_config = EntryPointRunnerConfig { layout: LayoutName::all_cairo, - trace_enabled: false, + trace_enabled: true, verify_secure: false, proof_mode: false, add_main_prefix_to_entrypoint: false, // Set to false since we're using full path. @@ -256,7 +288,7 @@ fn run_compiled_class_hash_entry_point( // Get the actual execution resources, and compare with expected values. let actual_execution_resources = runner.get_execution_resources().unwrap(); - + let opcode_instances = get_opcode_instances(&runner); // Get the hash result from the explicit return values. let EndpointArg::Value(ValueArg::Single(MaybeRelocatable::Int(hash_computed_by_cairo))) = explicit_return_values[0] @@ -264,7 +296,7 @@ fn run_compiled_class_hash_entry_point( panic!("Expected a single felt return value"); }; - (actual_execution_resources, hash_computed_by_cairo) + (actual_execution_resources, opcode_instances, hash_computed_by_cairo) } #[rstest] @@ -280,7 +312,7 @@ fn test_compiled_class_hash( _ => panic!("Expected ContractClass::V1"), }; // Run the compiled class hash entry point. - let (actual_execution_resources, hash_computed_by_cairo) = + let (actual_execution_resources, _, hash_computed_by_cairo) = run_compiled_class_hash_entry_point(&contract_class, load_full_contract, &hash_version); // Format builtin usage statistics for comparison with expected values. @@ -376,7 +408,7 @@ fn compare_estimated_vs_actual_casm_hash_resources( hash_version: &HashVersion, ) { // Run the compiled class hash entry point with full contract loading. - let (actual_execution_resources, _) = + let (actual_execution_resources, actual_opcode_instances, _) = run_compiled_class_hash_entry_point(&contract_class, true, hash_version); let bytecode_segments = NestedFeltCounts::new( @@ -387,7 +419,7 @@ fn compare_estimated_vs_actual_casm_hash_resources( // Estimate resources. let execution_resources_estimation = hash_version.estimate_execution_resources( &bytecode_segments, - &contract_class.entry_points_by_type.into(), + &contract_class.entry_points_by_type.clone().into(), ); // Compare n_steps. @@ -406,4 +438,18 @@ fn compare_estimated_vs_actual_casm_hash_resources( actual_execution_resources.filter_unused_builtins().builtin_instance_counter, "{contract_name}: Estimated builtins do not match actual builtins" ); + + // Compare Blake opcode count. + let estimated_blake_opcode_count = hash_version.estimated_blake_opcode_count( + &bytecode_segments, + &contract_class.entry_points_by_type.into(), + ); + let blake_opcode_count_margin = + estimated_blake_opcode_count.abs_diff(actual_opcode_instances.blake_opcode_count); + let allowed_blake_opcode_count_margin = hash_version.allowed_margin_blake_opcode_count(); + assert!( + blake_opcode_count_margin <= allowed_blake_opcode_count_margin, + "{contract_name}: Estimated Blake opcode count differs from actual by more than \ + {allowed_blake_opcode_count_margin}. Margin: {blake_opcode_count_margin}" + ); } diff --git a/crates/starknet_os/src/hints/hint_implementation/kzg/test.rs b/crates/starknet_os/src/hints/hint_implementation/kzg/test.rs index 79b3f674645..5d108b1d558 100644 --- a/crates/starknet_os/src/hints/hint_implementation/kzg/test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/kzg/test.rs @@ -1,6 +1,7 @@ use std::sync::LazyLock; use ark_bls12_381::Fr; +use ark_ff::{BigInteger, PrimeField}; use c_kzg::KzgCommitment; use num_bigint::BigUint; use num_traits::{Num, One, Zero}; @@ -9,6 +10,8 @@ use starknet_types_core::felt::Felt; use crate::hints::hint_implementation::kzg::utils::{ bit_reversal, + decode_blobs, + deserialize_blob, polynomial_coefficients_to_blob, serialize_blob, split_commitment, @@ -108,3 +111,31 @@ fn test_fft_blob_regression(#[case] input: Vec, #[case] expected_output: &Ve let bytes = polynomial_coefficients_to_blob(input).unwrap(); assert_eq!(&bytes, expected_output); } + +#[rstest] +fn test_serialize_deserialize_blob() { + let blob: Vec = (1..=FIELD_ELEMENTS_PER_BLOB).map(|i| Fr::from(BigUint::from(i))).collect(); + let bytes = serialize_blob(&blob).unwrap(); + let deserialized_blob = deserialize_blob(&bytes); + assert_eq!(deserialized_blob, blob); +} + +#[rstest] +#[case::simple(vec![Fr::from(1_u8), Fr::from(2_u8), Fr::from(3_u8)])] +#[case::zero(vec![Fr::zero()])] +#[case::one(vec![Fr::one()])] +#[case::regression(BLOB_REGRESSION_INPUT.to_vec())] +fn test_decode_blobs(#[case] coefficients: Vec) { + let raw_blob = polynomial_coefficients_to_blob(coefficients.clone()).unwrap(); + let decoded = decode_blobs(vec![raw_blob]).unwrap(); + let mut expected = coefficients.clone(); + expected.resize(FIELD_ELEMENTS_PER_BLOB, Fr::zero()); + let expected_felt: Vec = expected + .iter() + .map(|fr| { + let bytes = fr.into_bigint().to_bytes_be(); + Felt::from_bytes_be_slice(&bytes) + }) + .collect(); + assert_eq!(decoded, expected_felt); +} diff --git a/crates/starknet_os/src/hints/hint_implementation/kzg/trusted_setup.txt b/crates/starknet_os/src/hints/hint_implementation/kzg/trusted_setup.txt index d2519656fb2..47d17784051 100644 --- a/crates/starknet_os/src/hints/hint_implementation/kzg/trusted_setup.txt +++ b/crates/starknet_os/src/hints/hint_implementation/kzg/trusted_setup.txt @@ -4161,3 +4161,4099 @@ b990ae72768779ba0b2e66df4dd29b3dbd00f901c23b2b4a53419226ef9232acedeb498b0d0687c4 8d27e7fbff77d5b9b9bbc864d5231fecf817238a6433db668d5a62a2c1ee1e5694fdd90c3293c06cc0cb15f7cbeab44d0d42be632cb9ff41fc3f6628b4b62897797d7b56126d65b694dcf3e298e3561ac8813fbd7296593ced33850426df42db a92039a08b5502d5b211a7744099c9f93fa8c90cedcb1d05e92f01886219dd464eb5fb0337496ad96ed09c987da4e5f019035c5b01cc09b2a18b8a8dd419bc5895388a07e26958f6bd26751929c25f89b8eb4a299d822e2d26fec9ef350e0d3c 92dcc5a1c8c3e1b28b1524e3dd6dbecd63017c9201da9dbe077f1b82adc08c50169f56fc7b5a3b28ec6b89254de3e2fd12838a761053437883c3e01ba616670cea843754548ef84bcc397de2369adcca2ab54cd73c55dc68d87aec3fc2fe4f10 +97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb +ad3eb50121139aa34db1d545093ac9374ab7bca2c0f3bf28e27c8dcd8fc7cb42d25926fc0c97b336e9f0fb35e5a04c81 +8029c8ce0d2dce761a7f29c2df2290850c85bdfaec2955626d7acc8864aeb01fe16c9e156863dc63b6c22553910e27c1 +b1386c995d3101d10639e49b9e5d39b9a280dcf0f135c2e6c6928bb3ab8309a9da7178f33925768c324f11c3762cfdd5 +9596d929610e6d2ed3502b1bb0f1ea010f6b6605c95d4859f5e53e09fa68dc71dfd5874905447b5ec6cd156a76d6b6e8 +851e3c3d4b5b7cdbba25d72abf9812cf3d7c5a9dbdec42b6635e2add706cbeea18f985afe5247459f6c908620322f434 +b10f4cf8ec6e02491bbe6d9084d88c16306fdaf399fef3cd1453f58a4f7633f80dc60b100f9236c3103eaf727468374f +ade11ec630127e04d17e70db0237d55f2ff2a2094881a483797e8cddb98b622245e1f608e5dcd1172b9870e733b4a32f +af58c8a2f58f904ce20db81005331bf2d251e227e7d1bef575d691bdca842e6233eb2e26c2e116a61a78594772b38d25 +b3c1313c31ec82da5a7a09e9cf6656ca598c243345fe8d4828e520ade91787ffb8b9867db789b34ad67cef47b26ff86d +a8ed8a235355948e0b04be080b7b3e145293accefb4704d1da9050796b2f6870516c1ebf77ae6a65359edcfd016c0f36 +80e792d5ba24b8058f6d7291a2ec5cb68aab1e16e96d793128e86815631baf42c56b6205c19e25ce9727bd1fd6f9defb +816288c5d726b094e3fdf95cb8882f442c4d9d1101b92c7938a7dfd49bc50636d73ea1b05f75eb731c908c8fd8dee717 +ae009128d128ba2e1519bfa7a0c01ed494a7d461c3aba60f8a301701fed61fe4e31d6c79ce189542ae51df91e73ce1b3 +96a866d60a9007d05825c332476a83e869e15b11d7257172a67690ea9bd3efea44bf9c8d42191454eb04fcf110b16396 +8b250a2a06419adb9b611e89f7f8f2990aa301949b533ad3bf17c4a61ab5f5be0b1d5e2b571864d13f1bb75805c7795d +8450f49facf2e620fa45ee90e1801178842d927a2a25fc6ed7ba99a4eec7ae40eebfee41028eaa84f107f4a777694976 +91049080cf659c0985a22d1366e59191bb89663f922e8168b9b7d85c8a73d74a6d9dceefd855d3d858b493670c750581 +a1e167aeb2008087f3195926f1985c0a459d6ec57237255b1473a96de4e2c1cf766127c862c7dc853a6909e67cb06cf7 +b667c0d4e26e20698b07567358625d5f003839c92de8088e12dbd74a6f6a3156b4ea8d252c9ad62af5f6c4fec1cf6cc7 +8e4b5e304c0b1b161ae3e4b68b5e3ac66c42acd7c1ee2458044f6527c508a93995e50894d72d57c1350f91afe72775ff +8c642640aa7915421cdc21fd639f88a42052b1cfa358ff7702e60793a92b7b5926dae15a0c8f8f59cd3013f01c159ba3 +a356f35e713cfc283056bf539de54a21731e61efb4c47319f20de4a4b723d76a33b65f4a67d298b9ec5c2a1579418657 +93ce204146ce95f484dc79c27919a16c9e3fc14a9111c6c63d44491158d5838117d20851cc3227a5e8ba6ccf79e77f39 +b585664cbb9a84b52f89114e1cf0cf1171bea78a136dc1404ac88a11210b2debc3b7a55e702da93ff629095c134a295e +b6dfd444ec7fdceb14c6328f26ca12c3f9fc4327d8d8c68948e92e7e61262b82d833a65a9e3af6353ffa832b6da25705 +b4d4b8eb9ecfffe3f0d48fb4149c7b31aec1da7041ec03bd0750c52a2a7cbc3a7cfbf09d5bfdc56e3860826a62d0bb91 +a4e248e3d61db52da9683fef188579c470d65e2df9064726847b1599fc774049ffdc6ef2ae578d5ed7874f1298ecdf69 +a68a0fffc2e37d3183feb01b42234c0f4e510f9dc29d09c571e6da00fecad9da224cd0f31550070148667e226c4ca413 +86adda2ffecb77236c18005051f31f9657a0d50fef2a1175dfda32e74d5d53df825c10f289eb0ad39df0c64fc9bc7729 +998266d5c9c3764ed97d66fa9ed176af043999652bae19f0657c8328629d30af453230e3681c5a38e2f01e389ed8d825 +a05261554d3c620af0c914cf27ab98f5d3593c33ab313c198e0c40d6c72022eb5943778cd4f73e9fe8383392a7004976 +ad243fb3631bf90fedb9d679fd71fc0cf06bda028591ded2bd4c634ea7b3c2bd22eca2ab318fcdaa6c2cda1e63e1c57b +89b9859a04f903c95e97fb2951f01cc6418a2505eee0b5bc7266b4d33e01b69b9fe7dc56fa9ebb5856095be0925a422d +a68d118343a5bbfbbab95ff9bfe53aeb7fdbaf16db983e6f4456366df2aa01fbdb6ee9901cb102fc7d2bd099be2f1f3e +b49301f25d5a9dd2ec60ddb0b4b477291958487efea9e54dc0e4ef388f03b8bbadd13259d191f7a0b7513876767d8282 +8b93df7fb4513f67749905fd43db78f7026589b704ebb9ea3255d0ad6415437799f40f02e07efccda1e6fd5e8cd0a721 +ad88769ace96455da37c3c9019a9f523c694643be3f6b37b1e9dcc5053d1fe8e463abebdb1b3ef2f2fb801528a01c47c +80f0eb5dcbfaaf421bf59a8b9bd5245c4823c94510093e23e0b0534647fb5525a25ea3aeea0a927a1ee20c057f2c9234 +b10ad82ea6a5aeabe345d00eb17910d6942b6862f7f3773c7d321194e67c9cced0b3310425662606634dcd7f8b976c04 +82f6fd91f87822f6cc977808eeac77889f4a32fb0d618e784b2331263d0ffa820b3f70b069d32e0319c9e033ab75d3b4 +9436d3dc6b5e25b1f695f8c6c1c553dab312ccace4dac3afddc141d3506467cd50cb04a49ea96ea7f5a8a7b0fc65ef37 +8e0a9491651d52be8ebf4315fbbb410272f9a74b965d33b79ff1b9e1be3be59e43d9566773560e43280549c348e48f01 +8809137e5d3a22400d6e645a9bd84e21c492371736c7e62c51cef50fee3aa7f2405724367a83fd051ff702d971167f67 +b536a24f31a346de7f9863fc351fa602158404d2f94747eebe43abf1f21bf8f95a64146c02a4bec27b503f546789a388 +b5cdf5a04fc12a0e0ef7545830061dff7fd8abea46e48fbe6235109e6c36ee6bffcb9529e2f3d0d701cf58bbfb6a4197 +ab15377525753467d042b7931f66f862cbbb77464212c9aa72d4e5c04375ef55f619b3a446091c1ba1a3b5d9f05e538f +905a75b943ad017ff78ea6ddd1d28a45c7273ee1c2e5e3353685813793ead3370c09cabd903fcab9d8b1c6961372d486 +8147df4324faddc02fb0896367a7647b719b6499a361aecfdd3a34296fa6768ad31c34f9e873fd1e683386c44651883e +ac91d08570dd91f89d2e01dca67cdc83b640e20f073ea9f0734759c92182bb66c5d645f15ebd91ed705b66486ed2088d +ac6295ef2513bbea7ef4cdcf37d280300c34e63c4b9704663d55891a61bf5c91b04cc1d202a3a0a7c4520c30edc277c7 +b604be776a012095c0d4ebc77797dd8dec62a54c0559fb2185d7bac6b50d4e5fd471ac2d7f4523206d5d8178eabd9a87 +80ead68def272ce3f57951145e71ed6dc26da98e5825ef439af577c0c5de766d4e39207f205d5d21db903d89f37bbb02 +9950b4a830388c897158c7fe3921e2fe24beedc7c84e2024e8b92b9775f8f99593b54a86b8870ec5087734295ba06032 +b89ba714adabf94e658a7d14ac8fc197376a416841c2a80e1a6dde4f438d5f747d1fb90b39e8ea435c59d6ecda13dea1 +b0c78e7cc60bd05be46d48fbb0421a678c7f14b8d93730deb66fbe1647613b2c62b5075126d917047820c57fc3509cb9 +a860c4acc5444e9ae987e8c93cb9a5f17d954d63c060cc616f724e26bc73d2c54cd36e0492d1fde173847278e55942ba +8fb8269c9d5c15428e8d45da1251e4c4a4b600d47da0caea29fef246854d8fb6acae86a8e6440d0c429d8dd9c2dfee0c +96c5d8eb6fd5c525b348ee4335d200139e437e4be83690af0f35b7f336a7cda8c6d2958647988b84da9f2dd7bbb7710b +a7f62141c4346cc14e9823dc38ac7d587b0427022afc1498d12ee2c43f6ac3a82167057e670dd524b74137f8c3ceb56d +956aac50d06b46a3e94397f163f593f5010d366aa2d816c2205c7d0f47f90cf0f36c169e964f9bcf698d49182d47d91f +b812899bcdc0e70d79ca729cb01104bf60e1357b9085a10f64f3ba9865d57e9abd0a505a502d4de07afb46f4d266be2f +abce02c7e1372e25d40944dc9ece2904a8f59c8854c5f2875fe63ace8ce37d97881f4f9ab4f7bad070ec8e0daee58d3f +8fb13c515b2d6abb4e14ed753fad5cc36c3631dfe21a23d0f603aad719423dd5423157eefcbd9a9c6074e155b79eb38d +a9ef67304dc297ab5af778cf8afa849eeac27db4b6978963e97b95ef7a8d3264d0d07775f728c298a2b6daed2ecf5053 +a9b975520adb066e2ff2a4cde53284c23bc84261a22dc43b1634d99eff8e7892e46bb6e6da7319c9e72788aa9ea7a1ea +a6eaea4ab4206294474d9b956d9d3188d558a5633de2bd05df0d3bac03dbcbe4ed85406349c1d2e660b77c6da1f5bf8c +af4a19f77290dddee762e1e0d4bc9945aacea3f75756ae46cd3e58a8f74d1b5db73e4834687946b0f39191e32f2fed0c +aafa6523f58f1a4cabc924c86d842816d606afeea21fa4b2b8b9573425810fdcc41c98888318e868f9c05e2be12178a3 +8ef38fba0a3fa4ebe985239c8b759c22aaef0c57e6f39050a651c869487803b0d1e389c3d958fb5a7f37740f050ac69e +b07dfc9f85913c608ca7596a2e361f05e4853fad00e796fd492d247de6414892ce160f627669b1ba933b6ad726415d4e +94da679ad1d78b2bff5283c938f17b2a7d6e9cbcdf59d340e6dfb652951c7a9e852ac0590f99cfee9631b9410f6f00ea +98a907c9c021a5b034d3720197c160a82c4b7146cb73d48efeed99b9d0c6b831812cf80ac7e19e85a676a8cd3ead72de +adb746595466a12929019d0048cea33236b05c1229d2eba73b259a18a786f2bc3f05fc0598d8ce253cecb80bdf679aaf +a2fbac016996d68f9027a157b0a3f6a336144a798d6113adfcda3a5d05b62c31f108f112aa915906aef22b7f83b9228b +81841dea1904406d1b6fa49b4b3f7f6cb40b7646cf44d36c9fa07e3dee29f8e47324b40d8356ddf653109673c3374e9b +a3edbb8aac5e60c775775cbdb19067341b2e2530de48738e84c2c07151241ee31f0d8333bf20c2bc9dcb7b2e638a6b5e +b8aa6890e22964828787ce86460d3a32f12a655bb5c28de500f2fcf6b61e3334640ec6ba96029a4912af0d18df4b4139 +8ca43169f04243ad0fdb0152de17c60d9e31ee0ab520970fccd98590e05508821a183b4b367967e60d53c2c826ec5dbd +b179fffd9df8c00486c5a8b9327d599f5a11745ef564f06e126849b06fe2f99273c81f65bc941efb0debaadfecbfec1c +acf068f1c2b1926279cc82750ce21b0d6b0bfd0406f0d8bbfa959bd83935932957c7f6b8de318315bf0b75f6ee41a0f2 +b97831da260919c856e9f71a41687f5979bc16f8a53b1037285b4a2f9ce93af5cfe70bf0ad484744827fb55c847b58eb +aff50b0bd907383b0c241727af364fe084d021221bfb1b09fb6c1a7752eeba45d662493d590f1f182764b90b25f17906 +aeeef044c14e3ad41e1235c9e816e1eb49087fd3abe877b89b3bade74459186126e160bb569bcd77779e701b19b5f71a +8483deb2b7001ca7c438fcdca8ca6aba96c9cbc4becfd9b16a6062705eae270011bcaedcae69bb54630d8c78129e57c7 +aeee8d24be4ac0d9784c029e239fb5e64316ce29b88f47394cfaaa8bb966a72061bff72f99d02dc51c9705854686e77f +90ae09525a16bb2422169e15d6831c87968a14ebc0d1d27e11a759839c73c655b9d33ee5b12f275d6f440688146fbd2f +a3a41fc7fefef101422465e506bea7f3ff23c26fe35f5732b86f5f2471fb93b37ebc339f84c6be1e8d22abc812c2e212 +86f4b5293e8aea4af1f1fb05dcf99714cb3aff1cfc849b1bb73524061c921c9da9ad92579a852e1889da29d952f02fe5 +8932ef39d4050a1e9dc0fd8afeaf159472d71c5c27f458c69d2730836606ea56e19c8c4febf2535f930d3260e9bc7637 +86307b9f3696bb21c20e4558e30310389e7367803c353d437e9b696039a0ff054d9a4953b75237ab1d1dd6f71118c189 +96e57730e683ef5b550c91de18b19ac73879f3e26234297db68d28747ed0953beb0f3913cfb720c602720bf9330685d8 +b04a19ee70123782e47b238abde55baf60ac0c66292a998af0d14afc8bbeb1134e557b94cd17a020084631c09a0d3c02 +829abc8718be8139569fcb2c398962f38f4201114d30e2b2fb23566f8a27a5c380f5605cec543415202a12ed859e33f6 +a0744fa488c8fa92a722c5fc4ef5a47dfe824eccd87d26c8bab9c174cbb151d44b1b29082c48652f03d3177e5ec86001 +81d4035ae9fd28bdcd78b135cb54955d3b685a527319df6ee7e904b8e6d796f5f5a5f5035ee1de750c4cb6050e452b9e +b205e8c2ec24d7104fa0106c09ad34b5a912c1adef553fb718838dd627355993c2ec01055c11d00b2c75b68e9516d44b +b12d09da7968fa7394e449624fc7174d1d76c069ccb03e140d4d87a2d3f6d1f7b9cfc930f0c80becc673406ebe63f08e +b23752c158695da85048fdf38b395681cc0e8998630af8a9ed41efbda08c9964c2dc8ae6e53377264be4467d702c0de4 +b0d84582fd73628d96b8c1ec96197697c41a963542451a2ade0890af0d33c7161d0f18e1a1ce2c168ca2dc1e9119d55e +8b877e618b469aa187632e410b125d2999d5738fd66d482000706b51fd904a0c7e7daa8c9b729fa33817bbc4154cba2a +b1cfc8a7551b601723b937d497d01dec3ee7614c2bf13d430b1058d5ebc1406045009ff02c2ac15bf8cf16f860193d1e +b6d9da84f97b21e13175bbb0b5cc8e79e88b470c87a3e115726c1bd98e0288526c58f3faaa8aa170ace0cd6a60852525 +ad2e773c2d527671ca5fab7085dde4da31cd35f45d4315dd95d8893ff5fb900494dca08eccfc1a2fc7bf7c7fd2fcab97 +8d5a79b34aeb761d4a0c73f09f02e9548e6d382c33ee6887a759ab05762b490b8a549ef2933c7e3a46415c154c0221c0 +b6f2cbe81bd0a7298403be392f8456bed30aed7ef30216959357698f789affd2942ae5fbaf3f48ecebeb7c273b20cb57 +b5b6c45d99cea7ce6a1dc134aff4a8f630f299b42bd59592a7592345f8cd35bcbee944e61b0723de732fcad6e4425b63 +8077d64dfcb2418974e956ea6dbf8a4c05b25d2a025333ad7e2a379f1976dc036771403383a51bfa3476c9c619ef8bef +ad2e0a9d479c77a5fb73b3613a177fdaad50dcb50fed50e756ba18164c153af30b07fb2565e80ff7469f1b0338b7b5de +81017d1d80a6b6df4e99d0d7f85a8180b5523e8fa2ea2672fddff604933f8a113cab27fce098dcb454d7d1f7ed266e04 +852355479d68e76c7febf6dfe2ef8e80d575c0d3bd52c983803592021cfa898c571c0b884412c21e66f0dbfe03167b53 +98e1bf8ad48421467c93b9f72b47dded7c41b4fcd36ea55ca43ab24b0d0b876f5a731f422579b7167c7138fad2121266 +803369314abd5422019ed4b0ef652b4dbe97ef5a87b0ea373eec9628b64a12120b2c3d4eb53db405131ff786d14c7ac6 +adf2613fc34f73e1160975c140e925ed84d254e03cc3bc7fc1d19957b499c9ba9d9e4c1639981b594a7095c0a52c6757 +a2f6a68efdff6e4173c00692abcfdfcdaf6f8b62369afad3dafaae4f2f38c4860780b4624d185e20e4f4498b75b5fe94 +8b1658aa0e119fb8401d486ed08d60240d26a8623ef9788e3b45ad09ae31259395b021bd16be395139cbb7149714e764 +a7dd8bf21121285e00672ee8bb84e0cb39b2496fb53a26e35dfbca7f2b04e9a9ff9db15f53fe63fcbeafeb2deeaf2ca4 +b6d8d709e44bc18f3b41d69608edce60c02bcba48d3b7e2fd420842657f0665a7343246dea149a25e8f3416284abae66 +aaf744ca5e9bcb63e3e2939b7a1e96e4a93c88c76bec0cf4294dd7db95cdd3f6a7d92196e352d08680e2328bc4592899 +84434b015a7c398d35f1ec71fce455d62ba4ed4f62da042ec31bb2b4db47073314354cd50bc322297a1cfe35138bf490 +8d70b3a3cd9d5dfefdacfa418c0b775a112a47ce538d33a560a519660009c3f141fd6221c18539129e9c0acdaceeeb80 +b8c6903412a800ec78a4c15f31c24385a267b0c0ece32fd31bbbb557fd70c3b2d60d8fc0f90fbd70f43baa1928ea30ba +8e391dd445ea06cabb433f057853f8159511b2f9bef41aed9ccd14e0a6fcd912bbaebd38fd5fb736cfde0fa34b7a4874 +a40cd988f70613df32babbd1bbc2f1b29ff1ab0147b01161555a81d56c9621657999bcdb1df38485f687afc51d5d0f23 +b6a008b4426b3d7b28ae04eee4698fc8ef6a35d89008ef5394da39ce582ce1a45dcfae9a33b90f6fa4237f3667803873 +8987280debfb175c3b44a2f152ea82548e4f680966f1fcbee9bf7d714e31bf8080c33f52705ef3aeee70544b22516aba +a78a51a2c11eea7680a5a0ae417a2981f8c69c396e06da621eadd7510a3664ade49d065617bec67b3de779548a4f4509 +a4d9163f0a1bc048385e94d5e0bcafeee1b18f28eb23505623b9e8ef16f3df76408254dfbe790e45f2884198060d388d +83dcae2568a0c518793c0f6e38b42f9ceb50673d100b556a17ec8bd9faeec84afe50b8d72422c6b2356959667bb8e2de +874731941be4474b4576226e5906b5dee89fc9b56a9870dcc7289c1a7d494d345ba6aba31f7546a16f9963283c05f744 +82c1cfab1f501189ac20147fc4631075dbf1abf9125b7d42fcb4f31cf73f3d6461b1bd08fdf6e45cc54bc08a7d5d51d1 +b978228286f5d4a10ce027b6bea3021affcaa805340ca4b5192c69e8c56db59f48e4a14a284ec015f53baf97389f62b2 +af125f4fdccd1c1b64fdffecb5ec7cf8c7392bbe476e1b89a5b5329c5ba4a526e58c11e72ab9de8a38d60af648d75adc +8411a41ec14295acab0d36389013535a80dfff6e024bffeb32fb3070762f61256419e8c51b2ad6de9dbe4f1e8e286912 +8ea67a91112a41f9c65515cd496f4b0cdefa1400fc06568eef000c9eae6dc250fb7622eb3f2deca10b37287cd96fa463 +8da99b6c55c31dee6a49aabb54da249d348a31d4416201a10c45a3b04b11e99d4ae9813632f0ee36c523b5cca62f6f49 +8b44656341e039e2bd83a19c3bb9a88f6209482e274f8cd4f8557b728e5948dd80b5745f621b96f4562928689314e8c2 +a02d424a615ba0dce8ed91f477e79852215a3a39d025059826fa278e7eebef19824b2a2844f5b3865a0f471b609a23f5 +a1f115cebc3fff3bcf233da27cef19eae791660f155d088003460f75567a550bef0722885010ddc384acdeac635939dc +b61a55ce9d143c17876776e064b58a10baf0ba13553c785c1e47f57b5f94c0cda8bc89d43d73386e57816c15b61a8ec8 +b4073f47041e20a8e548c7fb00e07ba3b9056c34eb4ab63bb0e7b48f8e338e8b56a17611a1b5f4c03b352450b86f1d69 +a7b1a07b213205b682fc5b6acb7e76fdf97b280c26621d8f3b76b7c1deb3511957da33a4e358c8e8f3d98b2a8855d67e +b797e67c2670fbd9844e8a68c585f404b035dc14bd4ec75c3f95f932c777f9db5d5f5df7629164af488fc1213035cc5f +99618200797b945f595794d6468e5c618649554ad9ba896330f1cc844090eb956ae9fc23132912f9047085c5f0c3bf7b +81194aa1319abf534cb3927af9adfb178a99d0e3e8c99ab1105f1d3b4fed40ec2971caf1d6647acb0c8d681eca53097b +80673f18e4978dbc226a6cd4b128a1259d9a7f833879c6e2fbe24d69fef2c3c23a51a4f3e8d88fa4533434bbb0723661 +8125bf6c7dbb2fb63aaa3f53283559f172c788223674adbeb6d5bd17cfe888e6b87a79aec774917f20ce911c1f85f8e7 +884bcdb1878b14fc38adc9fb8b4dd0b3afde404fbeb664f26ddfebc81736018551f23e75ce4cfe4865f610bcd454fbd7 +aec65c8d4be8316e98aa54888af01bc6703a0c5d04b69756ff39a0a947b66817ec59d76afe9f61a25749b5e890f03e02 +aa457aaa1b014a4c5a8992847a187a23321bb43452c98745987d038e3b04046102ae859b7a8e980eea978a39d76a88ef +a9832ee63b08e19123f719bfe2fe742125f32463efa966c7709a98ebfc65277670e9ea1fa2d2d78b96bdc7523b0c4c3e +a87b6b1b7858f96d55064274f29fbde56067064962cf3c3e2ba3110b22ea633bc037a74d23543ce3307a46208855d74f +897cbe4ab68a753020fec732dfcc052c7ed9905342b5a6fe0aa25c631f9ad9b659e0ee75d46f0df6507b6720675ee28c +97c3b5f0d54c1fc45e79445c3ff30458959e406a069f5bbf7979d684195b4fa0406b87c1c008f4075bc9e602ed863152 +921e65d582ea9322ddfad1c855331c3cac81f53c700b96db5305a643c084eb6793094e07944bfd41dc02c3b3cf671530 +8f23ef1aca02a260a3b65d25b110f28d3bafca44727448c8f2d03c5e77eda620c1721b06681bd816ee6027664d76352a +946a89b132ec0795aea9ff9dde7b77e7feafffe6e4a2f093042a7e6c71cd6ab87ce0ca914a1b5fabad4e1f96a795f163 +a01e2de9db33df6511172123ad6f7c64074237471df646b32dd9aff8c15278e2723108e4facaedca97e9f49503f8c792 +99dcdcde45b2ea3f15279936feede5f7d3b63ca4972f335b0559c2fa6f9faabd8127aa892a36deb114357ca906553ed8 +a3f8af37bfcf66b04d1896a4bd5d343f4733d4c3305369ac7e75a08f20f2004c10c642d2c7577f4e5c4d1f2cd851ac3b +b7294d15a3d674a56099f97a1adc9e82c15e90832eaf1722df110fc2abc8634c51515e5ad8522015498a3753b1fa8c49 +b4f27f5062ba7a04ea0048b3025b5e3d5b5d319a9e80310c808a5fb4e8e77b38c10a0f3172cb805cadbcc8bc66d36ec7 +aefe5decee0ae2dc372cc6cf4217daf97c4c908d145f100f0daf1ccdfdf641c78432c2e473e7e4b77dcdf2d4c2bb05f0 +acc84af7648a535ffd218c0cc95c8f7b092418c548815f1bafc286b1fe14f6ccb51b2044db3bff864d0bb70e88604084 +84d8e3dac0df6a22beb03742e1d4af684f139f07e2ea0f7fb27fc2d7d4f1e89b5e89f71af32ff115ed5e6092133535f0 +8ada001e1a03a823c4c056f636e77adc0f9dc08689d28de0d99e0feecab5db13abf37b41ec268dbdb42c75419a046c68 +87dac6c798d1744dff81d8bc3e0e04f3c9bf260e811685ddb9a9a8d6eda73927439b344f9a818d2103fad633de5a4a17 +ad9929a7d8a7d5d5954e48281a87e5c84f67e19110d73296b9989a09c76767a57a8115629239ffb4d99dfdf9c52ef6d9 +81ac7cbeef8ec35a5c3b61cc887080c29e6cd3e08af37e45830d17400dbacfb374dd07bf370b979828c3875b2027d5c6 +97f92c9182953b7e10f7a1bbb6b5b5c40b8275eb5a6eec1e29874c4712814749aa8c409651380216e1ff01d7b8511041 +a09794d0bbe7db013045d3fd857c1544fe6231d21afa3495fa300371f6301a3a0f4b8ea175b281503dd06078ff371ae4 +839bb58d320aa08116dd387a57a2b9bd9efc89c4cdfd82d0e47a00cabe644631d09be5436bd485df3b61b75ddf81a3ef +b1cdaa344f783757e8b9c1f84421da3c5be4c69f019a8fd4c1aa5bf1a63e8970c99e35c22cf3b48a0e6738bc6ba7ce8d +92af68e3216c78998208fb24b5ba0e645d0d3f5e28222b805668d7e9cdd6c033d3b22fd6df4c2d745d7f910d133cd226 +87640a4ea4e605e2204e5232b29a6c1c31152d83547eef14122cb76a0da52b8653801af48455a3ed713b9dcfee7b1ef1 +8147e5bf0c8f4731155ca0517ef3fae5a32b4d5d2d98ed0007b23893d8dbb7f8a1199c50c1750c2fa7c9cebe594b1bb0 +a76b4473c63c3ab6103c729afd2482822e4150f3155af39983b0ff0766c71cb622455ce6304e23853661eaa322219d18 +b3e2f05ca551bc3adec0067e4034aaffd72e0b64ac18ae25452c996927976c6727966e26d213b032521889be2170800d +a8414cd14cb3be658e9e0004ce511ef7063439b1cbc3166a11de030613fde4b59caad4e91d426927863c55382afbf476 +b2f0f8ab99f4d0ea785ac84fdbc00b20217b1df59b30b51d9d209d489d53b69dd5d82cdacc16fd1dd15c3a4001595f50 +8b2025d5fd658c9bbed619f3e3f6ac8efe7aeff8aa9401bd66a7ceb0062c44b353608ca073f95be99204f0a913bb77eb +94a46bc5a87291b42024b2137e623c70115b9c6b196604106bfbfa20f3f56ac7779763f56b580190d3cb2f1c648cada1 +aca9355545118d0769cacf69c4b23d6d68d229cd8f68f1bc0c847c05569c5af6bbbd8c4dceb637b4a6b3b5c83841bf5e +b0731992cab87c7116406b283a84707a34838bfa3284b0f6082dfabeaf41c5ac2b0ddc1b420547a1b0955aee92de2dc0 +b671f77588c0f69f6830a5b28e7d07ed161b81fa9791bb3a24aae6638e3aa5e186df74978a82549c370c18ebee04d4f0 +b5621ed841780f3e6681d880a76cf519cdd20d35197b112eeaa686764d57b5dfa78ffe1a294b6bc76b6e3949cd2a2369 +afeba2524659d00caecf089645611553187a6ed7102050f6dd20f5a19bed08ac7065912d88371ee06242897d58d652a4 +b78bfb83d44ced14a20135804aba3f00128c3ce1f302e95567ce4097b0d973414153fb305b9f156882a5a0554bf25973 +98510aede95d26b1adf214053eae051ffaf24894e2fa37961a91d0ff5392dd09388196648d95b73e90bd88f2587cc4bf +b35c682d49c295946b9f120fbc47b95abd9ee86d294abb003a92139fb825b509209562575015856a270eb3eea86397a7 +b9641bf685571dd9c478dd2033a1f1b11cd3a662b26502c78595863b8e536a189674a9a85f7a253453ebfd1b99fbd841 +b2ad37036a59b1c9b8457972665720a6868422ed8157b6810a9c0783006103be34ab732d7aeb8629653edd18fd0f1717 +af0920cff05179a3896ea6ea322c39adf91ada5bc40fe3f6fb1b1b4e121e907c904bbaa8ca00468b3749f3da144d71f3 +8e269672818ef1e2f9e0c8aa65c84442fcd9151d74bb8e870cee8c0e3fe24526e1a5388b430cef47b67f79b4e4056bcc +aa29a16fe00ea3d143b1032b1dd26b8ce638f37f95c085c7e777e8e2784bd724bd5c38b1583c61a6ec7c451dd78fd3fb +87452b7435911cc5f513b0c81b15aa04972ecbe3d7bbd0a5d676c96a8a311301c0e07fac925c53a350b46fbd3d4d0fc1 +869a81c351096f47748e41566ae7b77a454b1cdfaa41d34a5742f80df38fbf5cbb08924b6fdff58e3b18f05c62bbbbb1 +8b7bc1b0486300981147a40a449ada9a41afc06d735cce8bf0fab3ee94ba2e2ea57b1397e3cd31bc295352beb8334ef7 +93e93fc41adb2df279d95654921b4c2edf0d293dab58d0afefb221f777349ef88d0985b3447e3b935954a81f1580a92c +970fa7cdca8324faf3e62348bb50d78f580b4f43f2e1c11bd8382d48d0074a3c55c6407203a0c9cb1c5f2163ba421ef4 +924983929e608d27e4a36d4ed919297869e3c64de51aca794d32d6e90aea546bf898d98ceca28a0b2187734821b78504 +8d395332529c703d943d68415d443332b5c1342ca9d9a59bfa8bd4ab63e93358c4b0dde6ce1f2e8ea9dc8f52ad7ebd95 +80200dda853e588256599e7f905add5d5ee7c74272780317694fbae39318ae9be05d5bcd7b20cf460069743f3d4ef240 +a287d51d6359c9ef7c7ac1b20e479ce7d0146dba5606397bd04b7a622cec642508d5b45d51b31de71f9763595b6ac88e +a320396c075175d6599225cf2e1de8c7cab549f6316c07feb0f6eaa21f06b2dd29ab14fbdf2af4543b4890ec0fd08a4d +b1e9fe230418d20368691058adcbbe30011bab3000422f0371015ff8bd09c60fb5fa85d18550d35b1c900977ca48f58b +9718fc26a51783b971744933f20490e9b5cd9162f86b84788c4c5217f5409e37b5a39d628b18e5b35a757acf67596321 +a0cf81fdb161f4f1b419c5e4caa36d4bdca2325f0cd25b119a30178016f171bd6fb88403e4e3aec026c4089f180d540e +8ab1e36bd04625ee794ef04c4dcb8e004d61aceb2b62438377f49ad95dcf025ba25eb799280004941e555bf7172af6fe +9257b9e3d14d37fc7efae49b0c68d36eaac546035f4a2654d566b3ce1b2c4564cbb03dc8ec66efceb768559a8a507a18 +945d1123b839637ab5154a1972c3c83a0ff34a3b1a3465de6ef0416b1950f649869a3ef88d7f1036648ee385265ce2df +81449639d708860fc0229c94f754f7262e8a3c7f67960ff12dfd15df95f57a9ffcee2013e81978b7703dd42bd5d0816f +a865481deaae5a690fd53892791e5fa729db283b75a525a11cdfee1ce17e8e7f0b449d25f20b3c1b43da128dbdf98a8b +98766812a65fcd25b853546e3bba618a3edc9fd61510e4f8ab60c038a7fa50d197abeec8776109df0f2119be9445ad00 +b1b8dd5379d903dc41d74e999b1ab693607a0d2905692f4fb96adf08f738e5d31f9d00df28ccb8b5856145ca552c3e3c +99d20be7b511bec78a8ed03c207aa4aa9097ba39d85e18f1b8d52f65431ab7e9a773c7b9ac3e8d8b25458bc91bd00703 +b1b7c3563fe8cb33c7d3e0b89d00bdd13e86452ff507c2e69db7b3af06f247f139155396e9b0278753310dc63940a10b +b3dc9c08451b1de7c9969b1e47574bffff50490f4a16c51e12390195d9e9c72f794790caf7b0a835d64e01fec995d3ac +aaaa4761a00022ede0809d7063d3532b7bfae90ff16f45e17a340ad4ebaa2fbac40728ccc5fbe36a67ab0e707566c5dc +8319a1903314eab01f5442d2aee6ae9c3f6edfda0d9a88b416d0f874d7d1d05d08bb482102f8ca70a4fa34836d0840c1 +932949a6e9edfec344932a74d4f81eec3667ece1e8b8ca840ce07ffd4b5d6d8f01657c764d64ac1b9190f876b136490e +904db1568128487e312fe629dd8bb920cecafd3bb9cad8b63e269ae0129f2f5c80cd82f0d81e7feca9835c3945a72d28 +a17280693d30dcd43c85de8f6b02d5f30cb9097274ad680cede1ef105c903615b4c40f3c6aaca478642de324972514e0 +8d5f76e093aee71d0cdeb017fdfcb13bd068039746de90690ce150a0bfdbe7ddc4d539df0f82c2d2890a40b191900594 +96fa1f2196a3883cdd73c66d28403cbbb58f6a939a3697ee0d308d8a076393cbb4be86255af986869230ee410c01bcfa +a8b74438dc5cabd70a91bf25601af915c4418d074327a9b01e0190c27d3922c89bb9b41e0b366e82e313edda8f21983d +ac9fdc1a9b2e3ff379eb2370979372e13c4177bf4574f1490fadf05a7073e6d61e703e2d8eed9ce984aba317d411e219 +a45a6c9b958169f2f8df70143e6ac3e2f6f969a4eed6fd9f1c620711bc2454739bb69f0094079464790c5429c0d8aedd +8901cbdd1009864386577842c1e3d37835fddf834064d9613b4559ea9aef3084204e1f863c4306f874141f4374f449ff +b6c582161691e3635536686825be9c4d7399d668a7675738417e0363e064dfd28acdbd8dbc9e34c1dab8a1990f1f0eba +89e89ddaf3cacc78428f3168549c161283ca8337345750667c98212717b21e7d994eae4e45bbddacc832a18df1d79276 +84be275627eed8e1a73c7af8a20cee1ef5cc568cfeea7ec323d7f91b44e9653e9aeed47c1896a8240b99dde545f0e1fa +a779a54ab4f40228f6e2539595fb8d509b70aab7c19e1928c1be69ec1dc19285c3898cf15e5f8b8bc725e13af177fe17 +92e2a49d2b9b36349d442283b17d46f8f9bf5932c34223015ce62d2f285e7363b2c12232be4a838b5b6cf08e694c094c +8b4e28c6f3f36caa2cfb82ba88066c830f8017bd35608b077143dff236f3181230166f5a5c02fa0e5272297331726aed +85fd77d46162ffac4b8adb25baff0eb0512a53a3d01638b3a376ea34702279ce21c8e7d8884308c03e00c9bcc1a9fd29 +aad5e46916ff1be29009b595d1d8fa160cc7aa01c7fbf3a68f445c87615790dcab1fcdbdceda533d182b6541f09f2f73 +948df7654726250dae393325addd3c0a20431c81f00470962190335ea4b6d9f7463d6f308cda46b92084c1f24390b1da +8f577474dea132676504376c5542b730b6604fe3d965eaa194659fd11c52233bd0b11ab62e198c0f442327ff1c00e501 +ae2f1001546db3e0c19700adad997cd9f765fe7a51a502cbcd9a2a07a3a5db79c8f603e05cf96d80b688cb6c9b6cd3ae +953b68e5d9561088dd20406ea7fb6894cba33868a38ace38fc30b5813140cb15dd6dd2171befae5b4df2e4a9658889d8 +86c52901655ff11419b084a04da8fc3596eae59d81d3461601c0baff59ba59e3d1dd0b7ce719e741a3e97c013e898579 +b9a72dd5eff73f9912a28b55de073568efb3eb0241a10b77a2bfd4f30c2aa4fbfe0c89eb345c9f07fb725660873cb515 +8e7353f5f2932e4ffd95811caf46c9bd1a53643c27eb41a4ebd211f230955cd71a8b27e17cfe8aa708d8514c0de67a66 +a096b8e66312a92fb10839ebe60189a8d1bd34dff55f7dfae85e4d2f53a1a4a88211c19fc84494f066358ddce82be131 +931c5cd82719d76596832b007969b5f75d65cffabb41b9dac7910300db677c1309abe77eeb9837a68c760bb72013b73a +8ba10f5118d778085122065b55dd1918fddb650cce7854d15a8f0da747da44d7b12d44fc29ad7dc38f174be803db74c6 +8c971deec679372a328587d91fd24ab91043e936ca709c333453d7afd43ee256d08c71cb89f0ab0e89ae119831df6d86 +a2ac28a58034fbd8fd518f409221bad0efec52670880f202e09c0530e2aabc2171ed95e99891790596ffad163d86c110 +b3354e3dfa8068aba4f3741152b9204baa4e342c1cc77e6dd1419cbaf8da1d118be605846b8609e997d6a62a11f3423a +a12ab65a213c9d95c24865fddc2dffe0cf9fc527dd6bcdacc1bd7271e79929a4ab3427a231f4f49d0530474e6cbc88f9 +90afd65b7e6973f8aafbe74da0f42441840d3c93bd69bc1bec8fa56824e7ca97ad1b427c8a85da7d588469bd4ccc50c3 +a09175940c59489bac3d3da3a4091270d9118948cbbdd57f2bcc63fbf45b8010651c801d3e58dccf42733ce1d6b446a3 +a843bbf286e3cecc1fe370ff1bcf5f1001bc2e95b34246625ff50d48ee62343e82fba2d25b8a4bd5f7b5ffe90920efa2 +a3c4d1003219157fdbee2707ce07afa6c2a64ae8e450182c307ed7f070024071f30b12c4b0032960ff913c74e73a9976 +b24af3f68d66f825d06fc3ff94fcccebe28b1a0d4ba29c48d3a3c953b9bf7ae6707f193fef25e2dcbd2b74e483c774f0 +b0f657f7723184ef7d7e4381143f1ac8020d8c6c6f2dcbebb0eaf9870d61a81f2d452596503311e46d1b38f625d4756b +b90091004fc8f6205c51bec68547ac82dba0f5525631e7632cf6efe54eecd9020729fbee6105d1b8012402d3b79c54aa +8e3fa187713c60eb0a416d6900a894cdf81e6b6b69dae0bb64f6287f3c3f030cfa85c665f7aace1eab4937f380b8f728 +879bf0784ccf6725c9cd1ea8c49fde31c91c605de1ea664a33c2ce24c277ee45d20b66309f98d989acb2ff3b77e13101 +af3f3a3ddc4e11abd627d5aef8adffa91c25df5f0c68b4d2b5d51e7d9af3395ba4f6f7ae2325a6672847e1ecc6cad628 +973e667289e796d3a40f072e6fea575a9b371a9997cf8961677f8dd934619ddc47c1a3efe91bae9ef95acb11a8fe6d09 +afa81c5606de82f46b93f4bb6db3fc0670f4e0d1091388b138a66b3827322d95a56168c951c30831d59eeadc227500bd +b83eff77db5b4c18574662942eb36f6261c59f655f8a9c3d3731412d0f257c8e80aacc995c4b2303058a1ba32522a434 +912e5ac9234b9445be8260393ff08e4859a7a385e800b74d1534eeb971f58f74cfb518dfdb89f8705d89fbf721439129 +ab27c8ece4a51d23e22c2e22efa43487c941139b37ea1182e96efb54ca4809d8245eae0ebe8ba94f0ed4457896fe11b1 +a6630585d104a745bc79dba266d9292bbdad346449c8ee8140a5e6e8a6194411df9cdbf3d3ef83468a536d4f052e9335 +8b8c128244da48e7fec641a882d0005a2d05c7138d86a293e6a0a97c76bf632b44767d0ce44663c975e7f9f9679e25e3 +87dbcaca67351a4e7d2297d7cdba4796d12f58857e7ee4abd0645563577ff33544a44cd84e50b3a3b420d6998de9b57c +b859ba43df259d7f8e7fac70bfd7aae546d57a5dc90e107b174a95bf7fd3cf00f740c4434848e69b2a7e6061f66c1ef1 +99d6e20978fefc40c6d310187eb2ad3a39296f189ee122ed64d74f81033c3069d44f7a9d3988a1df635b609603a17272 +99a5ddf3420cc0c92b21f71a805245608d4995ead447d8f73a670d26d33e26920d5f07bfe1f6230bd5f15978055b4253 +b936ac0944d3c5e4b494f48f158000abb37b80b5c763f77fe856398c664b0f1ddbcc0a9a2a672db9278f08b4bafbe2ec +b4af85fbf4040e35a686dd016adec037c99b47cc2e4dfccaf7870ee9e8c97bff30f3035992def2a9d4af323c0b3af8ae +a5ee32b8bd5f8fa9000da4da0bf00565659a43285393d37080b555d0166bde64d87317b2eab2d48a0e7b287caa989be2 +894d4ad58ecb1c9ebc4f5a97407082e56cb7358d7a881ba7da72321c5027498454f2c7fa2bd5f67a4b11d38c7f14344a +965be9eeaa0d450dacc1b1cc2fbf0d5d4b0dd188f2c89aaa9260e7307a2a1eb22db6092fccb662269e9a1abfc547cabb +805893c424aec206260c1c2d2509d2cb9e67ee528bd5179a8417a667aa216a3f318ed118b50d28da18e36c01f0805e3f +972d7040d4963b35260ef0cc37cd01746f1a2a87cedc0dc7b0ee7e838c9e4573784ea743f563b5267eb3905d4fa961ba +8c7156991d4c2e561888feaecf501f721b4174e7d14109e9deeac5a9d748301c07e11fb2b04b09799f0d34ff42cb77d1 +894722ac35af3d507e81d737d21e16c5ba04686f8f004aa75934aae5e17acd3e065b96e229eb011c2f34096f4c62048b +81237937c247c88e8e31e2c72412189fe59c1daf65c5513489d86cf29ee922c0bb08e5f7890f09f4ada7e5262083d266 +8cf62cda2fe0d9a6b42aa2a1c483f4ad26378c7cc2c2d1510a76df7560b07dba8528b33aaacb15f7f20b9d4c7c9f61f6 +aaf0921fb3e1920eee5d0acb59dcc268b42f4b435d60d25d30357edd7dd758d035919691bd15311d85489dfa2e5ee696 +92cec07be2247ef42002ebcaf65ec855611b8e893a5675796f2225f55412201b0bf9f4761924d0c8377b9f131e09e39f +8e514a62ac1e91773d99588415426c97ad63e917c10d762fe06ace5277a5c3bf3730e4b9e5d116f8493b9ab8687b70e3 +83932df2d923a5052468a3ea87f7b55c6a80ede3594046ee4fe233046570921822bc16555b92ba6aeabaef9b1dc0805a +a2b5bfb249de3472113fd3f35bfabf3c21d5609da62a27ea6aab5f309c9068d94bc58ba03efb4ec11be06306d59e60e8 +8106cf3ebe6f0507be8c6e8d137987315fe3689ecb75bb27980f36ba5efac504baccea0e7603549b6d126beccc278804 +a73ee70b6fe8c082443972102c453fc0e386852476cf22224fc0bfe554735c12f96037fbf10922795f4502c4f052b5f4 +932b27e175440169958504f3ed6400e7d6dcd5e716c19dcd0f15c56c04503ed133d5a993e111c016f141e32d68b29886 +96f7ce4595318e0b4a6b368f788ff82226aac676aed4ace343867f751de414453a9aaaabef6e6224ce5aedc3d5cf77c4 +a950c1e3bc9a14484997013d44d876374b939af437ae7c821c131fb886063ee9fe7214a25a0c7084f0b07b99412eff75 +a9dba3886ed6855303106a1bdd26010f294218684e1c178afcfea3f37a2f04fd01724a31d82de3449046617e3507a115 +87a2f776b32a6b550cf3ceeaf78db02819be74968d228b1d14e0d74a1cdf994bb500b7abef6619455e98d728701fac5c +8cd887b07e335edc0b27e6a660cebb64d210741395be431d79d570139687b056557159407459799a8197b6079644f666 +b81a61fce00588909c13a90c1caa150f15788786af443ff60ce654b57147601f7e70b95659e01f470334a220b547611b +8aebc51141544c5f3d3b99422250424b9800031a8fdfbf22c430907a3a446fecaa2392105d66d64b1c8e847240da4a6a +90db7dc12baa02f3f86d3edadf9434e2b9318d4f6f0eca08276b765dbb38d8eb0d08be2fe70adf2bf16ceda5db08d3ca +aa1839894152d548cc6ad963de20fb6fcc843bc9af2a2bf967c63626b8ad19e900894d6106265f38f3afccca317c22f0 +848e27b741496988a582515c0c8847b2bfc6a001259396cdeea1e1b1d2828ca3a626693a1bf4adf3a3d7f8b1fa3d75fe +a0aa11754d4ee136ac3ca609b17bcae77758763b2016544ca7921dddedd8aafcc7ad5f2b337c8bf53084eb8e43ea41fb +b8713b7aa1c112178195fdcc9b7024f46e6bc04c4e76c41abe620aa265287809200d98eaed6c9703fa97e81d6964f0ec +8605b5b33309e9ea6823542b85383c496794b8481c577497aaf99ba90496e794dce405be615bf92c7b6361460e6b82e3 +826fa34faa7f83e063a7bf172addfc07badabada59cfc6604fdf481d29085251c0a67a1355b2cbd374e2975934b84cb6 +b45d131082dc16fa53af010d43eefb79200dc23d2f3ee26af95ac6a5cebc49c84a9ed293e534ed16ff3ef9a4a25456ec +91bd6ce3c5396a7a0de489e49f0cdf6dce1cd2d0be7a410326423c3185bd1125ce1e610768be7f15f4e44b62f8834fc3 +903ffbe3d33fbf106c01c727dc3a385201a67ded70d4df623934882f69a3a96c909b027a124f3d70cb072b0046a149e8 +b405359db9d9ef4821a181b440ef2918c240595141d861d19a85867a5afa74d2972d22c988775eab441e734700bae4a3 +8abb756d027233c83751910a832b0ef4d28d100077f1c5d656720c94906f91d85dd0ea94b1cc0ed95b692efee14c786e +a78ee77ab476a41a3454160ba7ca4085d8b1f7057c63e76db8b07cf20afdeddd2250cd00771a6329133bb4ad48ccc20a +a41810271d8c37197aa9b3dfcefe3498e42f5978d3f3d59defff4676d6402d8575b40683834f184f143b6cfbfc859b3a +90c24a0750242660bcc6d487358a3cc015730538a0a8beb00ad5ac2ef33cb8ca8a62121e50bec8f3d2f43900f8e3134a +8b96c39695d864ef5796941754978a1fd612b369f6b77fe5ae6587beac936ee28190af8f0a3822b63060af35e49a5c8b +acde2548883d0e63c0fc257bb9dadd919aba60a985b69ebcfa1bca78acca42fc1322ec30bcc8e7c188818f858d04ad33 +895c86ae9ff8d95f2707d4838a3bc8ddb05b2611f0476f014b9c150d0e8332bc73285037a747426f09ac8179ba4e19fc +821761fe406e18bd86fa9ca9db99d382cd3b5c70c456f471fa3706d57763d147706304c75d54f51ce8f3115aa26e59d9 +a803a80e3e8f47dc3c59ea23eafdec017458eac648b360cd42cbd075e0dde6f6f450b48c7646fb1e178c04f82ae51a12 +91f40e1b6f588bd592829ce937996452c40be0fd6c43793c607866701ac6a8c7227e0891d45c6e7b1599382b0a3fbdbb +9408246d996a634a58689337f2526dfb3ba9ffef1d3ff91c32aa8cbbed900861ef25d6477308b67d76491edfcc70d65e +a492325a427f3df1c9c690c5b553daa8ac41f62f5ae55f425539222bacf959e2f67afabbba1732e120d3e7a6dcdf7049 +8fd0c3e15477cae228613a171b6e9ec29ddc63ef74854d99b638adeffe39f89f34346a42851e8445e855a9f2bbef0f57 +b735ed01fafa051004dbaad5e8c9e2faca8f6049ef9b590f256ea4d75b04594af12764ad4e6031735eae36f83179db93 +a7d35f43fca06c86b3425dcb68a87186834ba9740664fd657915771beca4cdc0fa2fc9b4c2e9d9bdad8ec33543ddfa59 +a1156e71e2db1b17df5da28747c88e091bd687bfee59d89096437ab4dc9a543fe5c5272d5023d72adbaab397a6fc94d1 +ab06a58bd81b33a411bade8d8c5232d38fadc2e38507159edea6e2e104b8ebd65ca02b05335118f691d44197b847a4dd +848b67a10f1e6ff8f5c228f226ef2ffeb67fb8f50925fc94cbb588d61896d9dc79726959e649898fd3354fe3ff7b7ee3 +aa933397361f32b388edcf832f0db172a38e756b34d5f7a4a050fa7325058006c22cede26ee27917e8f1b0f301792bd7 +89e49e7f02cfaae4a4b9c4180c9f6559d76e3a45774955859d4147970b1470dac37bdc9aedca1c32a20b045049161590 +adc1825d5ab94fc719f25d8c9773f4d518134ed88eb13ac33cb910b2be3523ef9ef88d9e4aea2418b806e20108317bf6 +96c4b444c8a023da644f3a343ebeeed19a8392d2ce175992461451c318a54273b76c3574d8f2dceda2947ddd34d1a674 +8aa7e97e87c8c5b29bbd51a6d30396a6be1fb82b716ef83800f2c36d5b85467ade7e0f59d2db82c310fa92a9265f0b03 +9146c32d99f02c3a6f764dcd9b4807f1585f528ac69dc4f84e4380f6fda4f9d5057c375671d51e7aca2b2b4140e83da0 +a10760a533d9bc57536bcaf65f080302086aa50225437efd64e176841544711828c23a15c49c0dd1f357d3f10722ab72 +acb0811777e17f7ae7aaba5f6fce81b759c067a4908730916195a2505c7450d0e6e2194c2ef0f241090597d58e70de47 +b24f161e9bcdbad56665e2490b5e4c7768390d4668cd69a04ed74739062dbe832636dd33cda89e9b0afa8c77e93fc641 +96b4d01106b831868a88ef016500ef2fa42d0ce87a37ca8ca4194a92a22c113edfe04eb2ca037329f3c1acc635148f55 +aebbb95fb4f7adcc8e7a217aeb73f9e037cbb873d08c1cd9d68c6c6834511adf1af8b44567fee84327599bdcb734dedb +a9bd8b17300532fb94d028659bcafbe7bbdf32f8945baf5db4cfaa1bac09e57c94cad0ba046b4514044b8fe81ea8596d +a5557cbda599857c512533e7cadcf27bf8444daa0602aa7499cafc1cf1cf21f9d16429915db7485f0e9a1b5046cf01c5 +8810307c40bc661c478a9747ebf2a30e5a5ead942d1ac0418db36ba5db0709c476f7d19685cabe6959e33ec1f3bff914 +8829b741f41f2c32e10b252d9338deb486dba2f23996a44cf1dd888ad967a589d51329be34d764139f372a1043f6c2e5 +a6b4728d18857c5fa082fa67bfb3b1d801e76b251b1e211a19c87cea5fe7ce757f943c85071f7a03a718388cd5690e95 +86da7f397e2533cd487f962ae58e87bea2cd50af70ef2df9ea0f29f70b5843cde664d30ec207ab84fc817f3851277e02 +8085776ef4ac6d42ab85b9d9135ecc6380720efd274f966544eeedf4684028197de76ecab919fa5414302597e1962bca +b05a065c733033d223ba13d16baa7a97bd8c8b8b1f0e59a9bdd36ee17e9922d48eb39bd180c168b122088a77f0bf321a +a89343fe44a93023dcc7ef71bd3bcb6786f68e1885ad260edc56a52445d34757f476395ba7ad35437f89bc573c7618dc +a114a9cd6105b524f3969c69faa2e09afe21753a93361a296f9e0e3b4e3e63726ddf2e6bfd3ddc046043e50bd44e539e +8a5611fec539cf681c05636bb580f29acc06f628bb012649ffa41ea6c1521194a5643d5dd843f09b6eb2c3bdb4d41acd +ade247c4011ec73ec90b72f35afa59a999e64ba5a7e664a4b30874fea53ba6a14a76a41b58a5f891a20d019e5f091bdb +905b5d96df388160ade1ffe210d0c6d1979081bc3de3b8d93ac0d677cc2fc2dc1ef6dcd49d3947055514292a3fa2932e +a9520796ca9fccd11b7524d866507f731f0f88976f0de04286e68d7cf6dbd192d0d269f0cd60fd3d34011a9fe9e144c2 +989a1edf4d7dae811eb57a865c8e64297837ffeeaae6ee6ac3af0f1044f023f1ca552bf00f1642491f0f0f20e820632e +879c8e63713f4935ed6e020559e140ea3073ced79d3096c152c430141272117b4fd9a9fc3eef012e81262df02ea14bd7 +95074738ac1540c0312274333acd1ecad9c5509fee883c4d9295fa8d8200f6e637c363de395f9fa612f05c0dc58fae88 +a770e4fc595269eb806b113ab3187ea75c8f96b57bf9fcfaf535f3eedc1d4d7e6285a20990575de0ff09f62d06ed0692 +81283e5dfb6423439ff513eca1cc316941d196df8da2d1069d2d0b63f5289e630af2fd4119bc0144c002d33313372dab +abd1b108e743887b78f698f2aba9d5492f87a22868d1351d705d93a1084fd45be67170c68a6e18b07f400d9a01cda8c2 +8509c3f67b92908cea8144f4e2a71631a66a61ac3547601c788907e52e380e5fe8ae4110aed95d13c67d3bcdd5b55a61 +8fa5a790ec5cce6d4114128c295390120869aac5490a82feebd3c37a167120df2e7fdfaf2a4050a7dfebf48fb093212f +944753e1ea7d8bc727d46a7702077dc01dc0c6574e8263a16579b57ee155ca5901f71bb347a01a9a922b329d3ff75135 +b46bc1fd4590b7a6275e20036d247c5909fc549c78e95b64ae7ed96e3b05bb044840f19f7650ebfe7008ba09fa83c3c9 +b1e47e4d88e59a06c465348c6cc4181d40f45b91e5e883966d370c26622c328415c6144aa2f61ddb88ec752482c550ca +8bd4f8e293e3f1815c7e67167618fb3b0ea76424bc0985908957cfcede36109378e41b4d89555b8c2541b4c447e00461 +a70589a867b2bfb63d0106083d58475d506637148549ed35c83f14e5c8de996e1b1f3447ecc80cf5cd134ef4db9d2fb6 +8048b80ba6131d07370162724127b0f7cb17fa7f71855e55e5a75bd0a9e4fd71b0d0ea2d16ec98858e458528df8d06b5 +97326cb94bae7530f4ec3235770c5a7ba042759e789d91c31fedbd979e3c0e6a2c69e2af3c1979c6fe0094274dbd53ce +a18e9c1d3eabd62af4e31a4b8e08494f4167fd4598c95d0123f39c46c53f9e93f76615900246e81a286c782ac37c569f +80309c59d4522b15aba617cd3c6238663e8b1c7ad84456346082c8f281140fc0edf9caa19de411c7e7fb809ca4fa3f4d +8e450c0990e2f65923f252311623038899eeff7b5c2da85b3a224e0ef7132588b291b782d53c477ecb70f34501466178 +87843f96f41484e254e754c681a65681b9ae5c96c292140368743df9e60f7e2ada58ca2bb95fa39abe064b2ebf21eeba +858e8d5bf2a1cf26d8af5036b28b831d450a446026f58a1734b696c18f1f41482796b91cab0e5b443dd2f0b9cffa52b4 +99627dd6bad8c05c5904cd23aa667d664da846496dbbb8452705c4ec01e1480e9c7295504a5a8529e4a0c842306b038d +b64b33256c18b2c886a837a0c0730fdfe73befb0e2796207c4dc592c5a33cd51f8c2ef47c584dd5773abf9ce9c1b0082 +944f6da2a1546f0bfc4d98c3e73c79e935e33d208b6be26b0b5f8df6d0e3b74a5bda649853b99281bd3a3ec799a7dd04 +a266d165435784d4e884640155e35b2a911b3f89e1e715986de419b166a36a341ba724877d80583fa3da566f6a828971 +adff2698409d0756e78c534032ee926560c13d578cb178d5073172d049ebbce32a92692f7e2033ec781b9b0d894ddce0 +a91933f110756c699c28bf9e24fd405bf432002a28c4349e0ca995528e56a5a2d101b8d78afa90a178ff1a9bf2ba515c +8e77839c0eb4da2d01e4053912cd823eddffbdc6b9c42199fba707ca6ab49fc324288b57be959fbfb11d59085d49324a +aa124517c76692036c737e987f27c2660514e12a953e63ff4bcb269dd18fc44dae95e282de8444bed09639ef6577af88 +b285deae99688f1bd80f338772472fa2b35e68887c7eb52c4ef30fc733812444c5cd110050275ad999d5a9b57f782911 +8877b0fa85b44ef31f50bdb70b879fa6df5eb1940e2b304fd0c8f08abb65f3118fa3d97ff93919038c1e452fb1160334 +8a89f3b50dcbca655024542ca7d93df17deff5c7d01c7da2bdb69e76b3e0b4490d85c800fb3debb4b0b4d20c9527f7ad +b7e5dbe36e985354ac2f4ab7730fea01b850af00767a6c4d8ee72e884d0fe539bb81f2e34638fcf5d07b7c8d605f4c06 +a85a1d78f6d4f9d5d83ec0f2a426708342d4e4a5d15625554e8452f6a843d9aa4db0c7e68caebdaf767c5b3a6a6b2124 +a518078a9dac63c5bf511b21ed8e50d1ccede27ebfe9d240937be813f5ee56aef93dc3bf7c08606be1e6172f13f352ce +91144eedebda4d1ad801654ef4ecd46683489b177ba1de7259f7dd8242c8c1700e15938e06c5d29aa69f4660564209a0 +a16c4657bc29d1d3271f507847b5a4f6401cee4ad35583ad6b7a68e6c2b9b462d77b5dd359fd88ea91ce93bb99130173 +85b855778f4b506880a2833b8468871c700440a87112fa6a83fd3ddb7e294b3a232d045dc37dfc7100b36f910d93c2ae +8d86bb149d31bfbf1fabcae1b8183d19087fd601c3826a72a95d2f9cedb8bb0203d1136a754aa2dd61f84b7f515acfa9 +acfe7264eee24e14e9f95251cbcfdd7e7f7112955a1972058444df3c2d2a1070627baefada3574ebd39600f7f2ea7595 +906bd14ecca20ac4ae44bff77cc94eb5a4ecc61eba130de9838e066e8766ed3b58705f32c650e1e222b3100691b3806b +8f2cbc7b8593c4be941dd01b80dc406fe9dfdf813ef87df911763f644f6309d659ea9e3830ff9155e21b195fc3c01c57 +a68eb15ed78fae0060c6d20852db78f31bebb59d4ddc3c5bdd9a38dbe4efa99141b311473033ff8f8ea23af219bc8125 +a95cb76c9d23fc478c7e8a73161f2ff409c1e28a2624c7d5e026e3cee9e488f22225a0c5907264545a73e83260e3a4ec +b76f90e55fa37c9e2732fd6eba890dd9f1958c1a3e990bd0ce26055e22fe422d6f0bcc57a8a9890585717f0479180905 +b80cc95f365fabd9602ec370ca67aa4fb1219a46e44adf039d63c432e786835bb6b80756b38f80d0864ecb80e4acb453 +b753c86c82d98a5b04e89de8d005f513f5ea5ea5cf281a561d881ed9ad9d9a4be5febb6438e0dba3d377a7509d839df0 +a664733f3b902fac4d1a65ea0d479bb2b54a4f0e2140ed258570da2e5907746e2ac173ace9120d8de4a5e29657ae6e05 +9479722da1a53446e2559bb0e70c4e5bf3f86c0ce478eede6f686db23be97fcd496f00a9e174ceb89ab27f80621f9b80 +b707fd21b75a8d244d8d578f3302d1b32bb2d09f2bd5247dff638d8b8b678c87d4feab83fe275c5553720a059d403836 +93214c16831c6e1d6e5a1266f09f435bbed5030c3c4c96794b38d4a70871782002e558d960778e4465b1ff296ffedad8 +8648f84e18eb63dad624e5fa0e7a28af2ee6d47c28f191be0918c412bf24b5460c04bf2b7a127c472914a0741843f78b +b67f61e75d6b773a6b58b847d87084b94f3cdac3daa7bef75c2238903a84250355a986b158ff96ba276ca13a6035fdd6 +ae9b094b7b5359ee4239d0858d3755a51aba19fce8ad82b0936cca48017523319c3309409ea6e9883a41bece2077e4d8 +8d1d8e1fba8cebd7a0e1effea785a35e16b1a10842f43e2b161d75add11eccf8f942d2ae91c20eef6c1a0c813731ea9a +b82bd387458e3603782d5e2dec32ae03890a3fc156d7138d953f98eff4200de27c224f626e3648e80cd3dfc684c4790f +a6dd02a89ad1c84e25e91176c26355e21a01b126c1df4d22546159dab9d502dbc69bc0d793a017c1456516e4aa5fa53f +a9ab74a5c5459b8500beb0ad13e9cfe2656e966dc9b4f3f98bec7588023b4ddebf74e4fc722d30423f639f4ee1b2587f +b03e5f33ab7ecec12cbc547038d3fa4f7ea0437e571891c39660c38d148212d191be29e04eb2dc001b674219b7a15a9c +925df4fc6e898ca55090ad1a8f756cc5014167a042affda5b24896eeb6aac408545134920586a8e1a2b997de9758b78a +98c8580fb56ed329fad9665bdf5b1676934ddfb701a339cc52c2c051e006f8202e1b2b0f5de01127c2cacf3b84deb384 +afc3765d374c60fac209abd976fe2c6f03ce5cc5c392f664bb8fac01be6d5a6e6251ac5fb54cfcd73e3b2db6af587cbb +8e7e98fb5a0b5b50d1a64a411f216c6738baaca97e06d1eba1c561e5c52809b9dab1da9f378b5f7d56a01af077e4f8cf +b724bf90309651afb2c5babaa62dc6eac2b8a565701520fe0508cee937f4f7b6f483fc164b15d4be4e29414ce5d3c7d4 +9665160e7bf73c94f956ecb8ba8c46fe43ae55c354ce36da40ccc7594beae21d48d9c34d1af15228c42d062a84353a0c +8600ab3aa86b408ee6e477c55572573ed8cfb23689bbdadf9fccb00161b921ec66427d9988763a7009b823fa79f8a187 +b0d8d19fd1022e7bc628d456b9bd1a2584dce504eb0bf0802bdb1abd7a069abbeeccdb97ce688f3f84a229342dbc1c33 +8f447d5e5a65bb4b717d6939cbd06485b1d9870fe43d12f2da93ca3bb636133a96e49f46d2658b6c59f0436d4eede857 +b94e327d408d8553a54e263f6daa5f150f9067364ded7406dcb5c32db3c2dffd81d466ee65378db78d1c90bc20b08ab3 +b58c02781b74ef6f57f9d0714a96161d6bfa04aa758473fb4d67cc02094cd0c0f29d0527c37679a62b98771420cf638b +8cfa0a687ea51561713e928271c43324b938aa11bb90f7ffaa0e4a779b3e98899f2af59364ce67b73a46a88748c76efa +95d6d39c814c5362df69116558d81ce6f1c65fb400fc62de037f670d85f23f392c1451d43341c59bc342bc31842c8582 +af888b384c52d9e04e4db6c4e507c2037eb5857e9bcc33acf84fc3a02d93cbde8cce32141fce9f5fec715b5f24d56356 +a7822bbc3c236fd58bd978f0fc15fe0b60933a0c953db6436a233441219418090ae0c07c490a6548e319029771cdaba7 +8c53729f750922e5eb461774be8851a3f40fe42eed170881cc8024d590bf0a161d861f5c967144d15cdcdc3dc6b5cf88 +a052a25a4aeab0d5bb79bc92a6ae14b5ad07d1baca73f4f6684ccecfc7ea69bc21eadeb9510452fdba116c0502dd698f +923946b83d37f60555dbac99f141f5a232728c6eb819a37e568c8c6e4d9e97a4229fb75d1de7e9d81f3356f69e6d36f1 +8cab82cf7e415b64a63bd272fe514d8b1fa03ba29852ec8ef04e9c73d02a2b0d12092a8937756fdec02d27c8080fb125 +b1123314852495e8d2789260e7b3c6f3e38cb068a47bdf54ed05f963258d8bcabaa36ccbea095ba008e07a2678ec85a7 +a685b779514961e2652155af805996ceb15fb45c7af89c5896f161cac18e07b78c9776047c95b196362c9ad5430bcb22 +b734dd88f6cc6329c1cb0316c08ade03369a11dc33191086c6a177cf24540c7ceee8199b7afa86c344d78d513f828e81 +b0bf492fb136ecdb602c37636ed4deef44560ab752c0af5080a79c9f76a1f954eba60a0bf6ba8bd7b8cac21848c29741 +a5c74682323e85ac20f912ab9c1d6e1b9246c4c829dca40c8a7d58ec07ea0ad3524be30623f351269552f49b65a1245c +837403b9cf830fb33ecc11a7c8433e07745973c36acdeb3fc9ea8f7d8d690d462e1250b7410f79f2f4180fe8f3962a4f +b03d64b944d49c83608f2c5b9c14070c025f7568c4c33d4eeb1da31d07f0bc5897e498b35b50d557ee129f0c3c68e254 +827272aab8bf757e2483156e00fbebe1093a58070dd3af9855bbf946c7abfb9c8a850a6a8acda8c620902f391f968b8f +84c4eb863a865282d321302d06b362f8bd11c2bb0090f90ebffedd3eb3e7af704cff00d39a6d48cbea4262942e95200b +b044eb91653dc55dce75c8d636308a5a0dae1298de4382d318e934140a21ca90e8a210e06fdf93aadbbeab1c2ef3904a +a8c08955a4378522e09a351ecb21b54025a90f2936b974068e80862803e7da2b5380c4b83b4b4aad0409df8d6c8cc0cb +a763a5fb32bd6cb7d7c6199041f429782deacac22b6a8467077fab68824dd69343ebca63a11004c637b9cb3129dbf493 +8c44c8afa9a623f05c2e2aba12e381abdb6753bb494da81f238452f24c758c0a0d517982f3999d2537b7279d381625ed +8613f47fda577cd3bda7c99b80cf4b2dd40699edfd3df78acb5e456dd41fd0773bc8da6c5e8cbf726a519b9fb7646ccc +b21a30d49d7e1c52068482b837a4475568d0923d38e813cea429c1000b5f79b8905b08f6db237e2eccf7ef3e29848162 +b9bdf4915f3fbb8d84cdfd0deedf2c9dc5b14f52bf299ef5dca2f816988e66322df078da2c54b934b69728fd3bef40b5 +993b45f389f55eba8e5ba1042d9a87242c383a066cbf19bc871b090abe04de9ff6c1438cb091875d21b8c10fac51db58 +a85a95d14633d52d499727f3939979a498c154fd7ebb444b08f637b32c1caf5cca5e933a2f5d94f26851ae162707b77d +b9874c7c4be1c88a9646e0c2f467cd76bc21765b5ab85d551305f5ec0b4419e39d90703d4ac1bb01feb3b160517e97b7 +ad6771177fc78812904c90594712956357de1533a07fec3082ba707f19c5866596d624efc3e11773b3100547d8f6c202 +a79f31921134f7197f79c43a4b5d5b86736a8d3ad5af1bdf4ad8789c2bfe1c905199c5e9f21e9f446247224f82b334f8 +a7f1b6c45321222a350a86543162c6e4e3d2a7c2dce41aeb94c42c02418f0892dbd70c31700245d78c4d125163b2cd5e +92abafe3ec9dbe55c193fb69042500067eb8f776e9bf0f1cb5ab8eb12e3d34986d1204136856fb115c12784c3b8dea6e +89bc761238a4d989006ca5af5303c910c584fe7e6f22aa9f65f0718a1bc171e452c43695e9f5a591725e870770c0eceb +aa0e44c2b006a27d35e8087779411ba2f9f1966a0f5646ff6871bcf63a8b1a4a7638751b94c9b9798ccd491c940bc53f +8736fe82862b8106e7fdab7b5a964d87ec291a74b8eb1cb5a6c046a648c1b686064ef3d52297043b8940bfe870c712f8 +956a3def1942f05144d8e9c3a82fd2d3610064b53b9eefde3d5594a8f705bf8f6849eb2c22181796beffeba43cc74ee4 +af27416d00cf97d5a1f4a1b6b51c010884cceca294f1151c3b684a3f83c3c8a3c30771df1166d833cbddf6c873c400c3 +aac3b8dca2336fc4ffc63c362df461289e4bbd3418c621bde6c581d3ecedf66e2b3e523d4db39e3d8ba014577bf85efd +94c3a8167f62074e5b28c2bffe4b6ce645439a9a0c5da3ca1b3ee956590a465d6f84a8a4dbbe9070ffbd6bbc734e4d62 +95e23ba6986d25ed4451215da05bd72c5491528271726d79a94c8cb16aef1c85b190d6c5b8a3a1191c7cafbab1dccf0c +953e3dadb5ad68f7de31ac09692948655d174fe16d88b96930ef35b331da7f1dbc4c17863cd07b4ec3135b5205891a27 +915d018f18b5d63cb3301c2bb5c6e85e75a88ba80663c964d06575b6bacbbe59139d030b218ce0998271d5b28c00b26d +8c871ba3dd138a908b2f7effeea0e71df096b23e0dd47cab10b9762b250abfd1221da94a8ee884e05bdf02271fb85a04 +96bad5c6ebc3080ecbe337409ae398bbeada651221c42a43ea3b7c08c21841ddbcfde544c9b8d4772de6f2ce92c0b963 +b5dbcd0b1c44c62108841558ec0a48df4b327a741e208c38b1c052321eda6e6ad01af71d49dfcdd445ab6fa6f0c34e6d +97dba59219b69e8aef2659d1f10bbea98d74aefff1f6451de3f41be39acbac0122b8ff58b02e90554469e88911ec3547 +b7e5682ec306478be4858296f5d03364a61f3260636a4242f984d351a02e8723378496beb30c4ca22def9c9ca193ea70 +9656a7a3df4d11df3d8bc35930dff70a5e78a488ca57bba20bb06814fc390fc6c7cb3f39b22134992aad196cced577de +8b269695aa63eb56d0324ba984279dc4c88e565321f1d61d553622bd4f1910d5eff68393d3a830eb924472bd478c2aa3 +9177bcd04b28c87bc0440268b4c8995c6790cad6039594971b2c177f0e197055231e776927d3fa30d98fb897a2ba401f +ae0e943973482001c4f214b9da82e1c27e38aa254d0555e016095c537c835d3702bc2de5c67b234ab151e02b3b7a43a6 +82fc719a7d38bf4787fe1888019ad89fbf29beb951d2fece8686d2beb9119d0c8c6d13bc598748c72c70d73d488140ca +b716dc66f87eb16b95df8066877353962d91bf98cf7346a7f27056c2a4956fb65e55cb512af278783887ab269e91cd76 +81d58cd8bc6657362d724b966321cd29a1b5cdc4601a49fa06e07e1ad13b05e9f387ca4f053ed42396c508cd065c5219 +b32ad0280df6651c27bb6ddbdc61d5eb8246722140a2e29c02b8b52127de57a970e1ded5c2a67f9491ae9667349f4c46 +b68a2eb64cc43f423be8985b1a068e3814b0d6217837fb8fbfd9c786db9cca91885c86899c50a1242040b53bf304ced9 +85887515d4e371eabb81194cbc070e0c422179e01dbda050b359bd5870449c7950e6b3947b7a4a0eb68199341cc89fc3 +ac5fff3c27dfbab78eb8aad37ac31cc747a82401ebf3644a4f4f5aa98d37b8bf3b3f4bd8a3428b32a127c25c9e19d239 +86fceaa6fbf8913553a9e1e907fcb1f1986d5e401a7eafd353beefd1899d571454fea96ff5b2a21254d9fb693ec94951 +b6778bb296d3f0de2531b67d36fdbfa21475be0ca48b9dfcc38f396c41b557823735ed0b583e525a2bae1fe06e04058c +898088babeb5b9866537d6489f7514524c118704abd66b54210dc40a1c1ddb0a1edf7fe0b6e0db53b836f1828ecf939e +b27854364b97274765f0fb8d1f80d3660d469785d1b68da05e2bd1e4b8cbbe04304804d4c8aabb44cf030eba6c496510 +8c55bbf3603dc11cb78b6395ccbc01e08afcef13611f7c52956b7a65ccf9c70551bff3ae274367200be9fc2d5cb26506 +947726f73cd6281cd448d94f21d3b91b96de7ad3ff039f9153befbb5f172db9f53cacb4f88c80a3db26e6a0f7a846eb0 +a7b733a05e97528812d71cecb4f638a90d51acf6b8fcbc054787d6deb7e2595b7b8d1cbe1aa09d78375b5e684a2019bc +8d5ca6d161341461544c533314fe0a6655cde032c2d96f0e4ea7e41098b8b39fa075d38e2d8c74e2d0308f250d6cf353 +b960e9f081393e2260b41f988935285586a26657a3d00b0692ea85420373b9f279b2f1bb2da2caae72dd2e314045f1bd +852a49c7388c10821b387c6d51617add97ba72485f52be95d347bac44c638c92e9c6a44ba0d32afc4d59178a497d944a +8412162a65147e1334ad5af512982b2b48eef565682b3f3e0bbe93fbc5e1103db9375a0c486bdb1b2c57e4cb3a8e7851 +8f52c3eb5d4f1e1e82cfd2b291d4910195427603b796f6c311deb35ef14a01a57a9e6cad39619ad108f3e86f384f9e1c +88d221088f2bf0103c53e44d0d96cd7881ec2b0a965db9121a47481771a8b796edd5ac23c4f9c208a171dab301f7d3bb +b49c3235e8b3617ed08a1891b9e2bcb33dbdacceb94ca96330555b7e00904fe6a749ced9312b8634f88bcb4e76f91cb1 +a85834215e32f284d6dfb0cbfd97f6cffc7b9d354e8f8126d54598bb42d7f858a2b914cf84fa664069632db2ff89a332 +aa3d48eb483c6120c27d9b3e3d0178c1c942632ff54b69f5b3cfbc6ad4ff5b2b9ce6eb771fd1eea8edf4a74c97027265 +a446cfded353cdd9487783b45846402b973cdeddf87e2bf10cf4661610fff35743cc25e8d3b5771dcedfb46b018a5d18 +80998377b3b393ef3073f1a655ad9d1e34980750e9a5cfb95f53a221b053ddb4d6985747217e9c920735b0c851d7551f +a35ac469790fac6b8b07b486f36d0c02421a5f74ea2f0a20ffc5da8b622ac45dfccabfb737efa6e1689b4bd908234536 +8fb1f6d8e9c463b16ac1d0f36e04544320d5a482dd6ffaec90ea0f02b4611aaca984828bf67f84dcc3506b69af0a00a1 +b6e818d61aea62c5ed39c0a22ccbb327178feebdabda0c9927aa1549d2c5bb0637785c4aed2a6d9a7b4989fa8634c64a +b4e7208d16018bf67caafe996d436113eac619732e3f529a6efb7e6f094d8ebea55b7be0e122be075770f5957b6ea6f0 +b691d38b552befac61f6d367287c38d01fec73b7f2efdb6713ca30314a37fb7c177eb111fe6bee657f2681014e07630a +9817587e418e6e7e8e97ae27067f17b55d25dfb14e98f63f530620c855d9a348c9fa571c8508e2741f902f8b9fdc0c5c +b6a6e5ca779ba140bf1d84cd5394ede8262f7479637ec0087a4b152243a1774ba916d8115ce759a3bebd1b409de5f2fc +b53d1c84ad766ff794bf497db3228efd2cc8ed5fc1958d89c1126efdff361610ecb45ea8e329b39035ab00a66c1259c7 +adc31333c507c8e0f4aa2934fcdca57fd9c786722a50dbd5404e129541f7ac182cc7373bf14e1e4e06e6cf94b31b90eb +a82b7fde4642d982d95cec669efee140ad797a2442c7f6620580527d163accbf021b893446cbb8038ea82fe25b15d029 +91f7acf8a8903979afa281646fdecb54aa4d2ed905748e156e92f0910de268fa29d67107d40863935d677d1de8039be2 +86fea71c6d43a7d93216a92fc24dfce8521fd4534a9558b33762d002081247867a6eff54cad7116023277fb4049403ad +8ae5369a7f9f4c91f3be44b98089efd9c97c08f5bb4cd8b3150c115ecd86288fa0865a046a489c782973a111eb93966e +b6fb9e829aa2c81c2d9eac72bb2fd7f3a08e0cd763532c2ce3287444d33cf48b3621f205e9603ec58525934b61a795a9 +83e35ca808d84e41fc92115e9f6e283e928c3a614e6dfc48fe78c33b6411262e7bfa731eadb1e1937bc03cff60032e1d +832fca5196c95098ad47b7d24ba2f9d042e1c73ad2273edd1c2ce36386796ccc26e8567847697f3fcc2a0536a2a2087a +8fdb7038bc8f462ab2b76bf7053362f9c030019f1b6105cf42219a4e620ecc961e3eacb16a8e581a562a97f1418b0128 +8d3a5a404b51b1ad8ce3b23970e0d5cc57b573922341008e3a952a1dd24a135e19e55b79d86a70cfd82e1c0e9630f874 +ba00c025c1c21c57c03cdfc0bfd094b35422281ff0a64b68b240617aa58c6b18800af5f2047d3ff9068bbe987d6c7980 +b468f0dd51964b3806b0aa04f3fe28a035e8f5567fc7d27555be33d02701a838b8dbfe1348b6422c4eac46d2c75c40c7 +8a73a18c97da9958903c38584b08d0e7e26993a5d9b068a5e0e1ee0d8a873942745cf795f94f7a3d3ba88790a9fbb2f6 +953a0a40c2c8102723736854d13b228698c14a02d85c8d2e61db1a768019ac305faf0d5db62ac976430ce087a5b20f1e +8998219da6b34f657cb8a621c890a52cb98c2bc0f26f26e2af666eebeadadc5e8bdf4f830a91d04aca8ce186190152c8 +8941e08c3155ad432236ed05460420a05dd0aaab30477493ffb364b14c00ea5b9183d30d3442b6321d2d20c36e4f5c7e +93f293ff7fb56cf5b03aee6f3ad2ad78444398ed5b3be56d7bf5b56b5aa5a2b980d13895dd57a5726d1b067c20cc55e2 +84a16f313e3f75e31824f58d19ab24c6611fb4c75140a7cadc3c166f68819547c1d0ff7f7d13f5d8ae30dff1d80e2aa4 +b6e3e830b15039d3e28b08f5465bb089eade11ee3bd80afe39e010df7db1fcf0c56d698717677a41ddbc91eeaf6544d3 +95e928e6dfff51351281568ae72da7d1edeb6e9fe01f30af0499e7505ba35a22b5bb919d41bb809a432dce83f3977663 +aabeeb60ca46f9b0232ff82ea7766dcab8cc5aaf9d23539f30174f9486640bc9312868ca493b59b314519fc399973e47 +b393a11e957d0bbb3ecf617b075b5906a3450b348e62916c04791b366f0a7397cccd6648440ac544bc30526e1f95aad8 +abb5bfc3964a6d246da60bd809d0ea6daf4f8222efdc12ceb6730194e85f413ee7eb03bae300abf7ea900dbbc3d08971 +96c1bd1d1d216a4bfbcf000c123f296c0d31e1684e9e3884c14df23bf528c8d599f82bb98fcea491716b617216a8e0be +92d1e570a56f1741fd9f3d9f488cc336421c6256c14a08d340a63720be49b0029e3780e3e193a2e22bf66cc652fa22a3 +8769c08551e3a730e46f8e5d0db9cf38e565a001dfb50db3c30fa7fa0e98b19438edc23c6e03c8c144581b720d7b33a4 +b850bd67fdf5d77d9288680b2f6b3bc0f210580447fb6c404eb01139a43fccb7ed20051999ae2323ea5a58de9676bfb4 +80285da7a0aaf72c4528a137182d89a4db22a446e6c4a488cf3411937f4e83f7b00ec7549b0b4417682e283f91225dfe +80520368a80b97d80feb09dbc6908096c40ff7120f415702c1614d7112b0b57f6729581c71f4a3ce794ac959a46494ff +9817b4c27a490b1cd5a6337e7bc7e8005fa075dd980c6bf075ddfa46cd51cc307ad1d9f24e613b762a20fc6c877eab41 +ad66bda1a3034ec5e420b78107896ecf36126ce3ef9705163db259072dfa438c6107717a33572272062b9f60cb89557c +876114ef078c2915288e29c9abe6b0ad6a756b5ee2930ba1b8a17257f3f0557602d1225e8aa41ce8606af71ada2a971b +aa3d6cde4c3b9d3d5d0c77a33e67f182a3e1cf89b0921423b2024236171955b34afc52b1f25b1dad9da9b001371771d7 +984d3e3a72412d290e3459339757af7520d1739c7af0cbcf659c71999328db44f407d92e8a69fea11625612c49eac927 +ae890d0faf5bd3280dcad20a5f90e23a206661be8842375fea2ab22aadc500849ffbc52fe743b376d46bb926cedae6a6 +b1f231f3f4d710c3fe80099faeb56dac67c1baf53b8fe67a9920fe4f90e52cb9a4bf19211249a6456613b28efe337f18 +8caa54b418ba609d16520af3dff2e96d5f2eeb162c065a1763beb926547b2cfb3ae41d738db2c5681a9bc8bc9e6b9a1a +932157ff56c5ac29cf6cf44f450c882b3acfbb9f43d12d118da3d6256bde4e6eb3183aea304ab6967f37baa718ffec99 +9360bed8fc5b6aac36aa69473040689bfc30411d20ffb7275ef39b9ff5789f9055d149383ce9f0f7709a1f9d683adbfe +98b5b33209068335da72782179d0c7aeeabe94b5560a19d72088fe8323e56db7ce65debe37a97536b6b8a0ca3b840b61 +89a385c11be40064160b030a1bb28c3921fc8078522618a238c7ea0f86f34717ed9af9b4e2e20f5128e5f7fc66ad841e +b615703cbc64b4192990cc7e4903b74aed6a0076ce113b59ef7719197ffa46fb29eb78ca56b49873487432d0625c0faa +90f0d77abae9d3ad73a218e5ccec505ad108ea098451461567ae8ef9661606ca8e78df53b5d628b20b7037bd24622330 +92e0e7cc4dfadc5fa0ee6da0c8de0493030db6e54ba0317f52f232a6708b732068b6077bd13a17eb7eb40b88368085b5 +a24dad20094985bfccc6df1343506ed3bf9dcbdf4b2085a87627a5d71f7568db067304e465f8f380c5c88e8a27291a01 +8629a45a10619354c84bdc2f6c42f540eab5a46f53f2ae11970433d7a2aef007897590bf31dfba1c921614c6d6fe1687 +84ac64040d4206f82b08c771f375da4b7d752e41d2aa0da20ce845f6bc1b880a855d3ee966bca19b8ec327b4b43e7f0e +9608e6050c25996c052509f43f24a85cdf184135f46eaac520a9a6e78e0d44a6cee50ebc054048c708aefde8cd6651c2 +a32032b0e0d7cc35e480c328f315327f9385adb102a708c9ba637878deb74582ae26bb6d6e5f8c9e3a839b0e0154b82a +b7e3c78d63acc6564a49e9f00b0a820b56d4f37a2374af1f7f1d016268011df9e7af0670ed2b0eee961f15aa948328dd +8b88bfdd353acc91ad0d308a43e5fb40da22c228f2fe093c6d6904d70f69c6203f56636ed898b05df51d33f1095ef609 +b1d7a430c51fc857af55047683fc18c453b013527196c5e1bf776819a3dffca802217e9249ae03f084e2ea03ad67fcc2 +80558e28a819ddb5e72e97c54be0f57c173ccf78038d360d190b7f1350a19577b8e3f43fa2f7bf113a228cd3b965b2e4 +b4b2ec44e746c00dfc5661ba2514930934fc805cdc29adc531c02d28ce3cc754414b0485d4ee593232cd1175f357ad66 +b57cee5d32835f76572330f61ccd25a203f0e4a7e5053d32965db283aad92f287645533e8e615137208383ec51b1fd99 +930256086b419a8a6581c52590d0dbd9f8a3564c79424198fca3866b786df2f6098a18c50dc4abd20853a7184b1ce15d +8e75fd01181cffcd618a983492390f486e8c889972a46c1f34a4e1b38f384e8e4efc7e3c18533aa2057da9f9623e2238 +b375d927dd988429f9e2764e5943916131092c394fce13b311baa10f34b023dd3571da02553176091a0738cc23771b9a +b9e28e4c0d0477518034d000e32464852e6951c8db6f64ccdb1d2566f5094716213fbf2fc0e29ac88d0e79f725e3c926 +963981e99392afbd2b8318d5a6b2b0cc69c7f2f2f13f4b38dddbfedb2b0eaf0584aecfcbda20a4c60789c15d77970a58 +a7804e1977aa77c263c7c001afa6cf568032dea940e350d6a58ce4614f1a91c13ae1c78bfea740c229dce2444556976a +8787204177da3cde6d35cd3497fa8774d244f9faa9f4bd91b636a613a32ce2ea0326378cf9c4cf475e73ef751b355c4b +895aeef46a07152a04ec812f1aa1fd431389fa0ef6c6e96a5b833e70ea14073bc9984757a8ee456dbec9788e74e6f0ca +8d17f0e5826783440d1f0ec868003510a4d9952bfe4a638e44a36d94482ac18ba70ef7ff773bdf7a3b62d714dcf0fcba +810d5e36b31310b2e054a666d3b3f7ed16dfcb1765532d87ca2a3920316f0187303c27dd113db145d47e8961062a6c03 +b4e2fb48ae04cf8580bb6a28095076c9b95e5f13122b917328f334d4ac8a8648ce442919e28319a40148987350ab5303 +b85549a313544fa1eb3ceb78473b7d3d717fc85b808de7b79db7dbd0af838ebb020622a7503f1cbacab688dddb648f84 +80665adee057088eae827a5fe904ec3ad77d8843cdce0322d535e0659b4abc74a4d7ddd8a94c27f2def5c34ac2c038ee +ad72fc19c2ce99b5b717e35528fe7d3ac8add340b02ebeb4889d9a94c32f312a0b45ea84d21c54f84cc40ee4958b72e1 +99d530c843dff89a47a5ee8c87303ab18f8a82b0d5b808fca050354b35da5c5a5594d55921c6362d6cc917d75bdc18dc +99c7286c293e1be21c5b2a669dfdfcd5aa587105d2886fc5a8eaf8984da4e907f7d7b8c2362d64a4f1621b077a2a08a0 +b4a39e1a9ed5d80c9563c3ca3fadf76f5478c63a98f4346a61b930c9c733e002f3ff02bc16abfdb53d776184cc3f87ba +9378ea71b941979404c92d01fb70b33fa68d085bf15d60eb1c9fc2b5fcdee6379f5583389a3660a756a50019a2f19a69 +b68e17344a2bc45b8e2e19466b86dc139afefbf9bad2e2e28276a725099ebac7f5763f3cb52002261e3abe45ef51eb1a +819e64dc412b2d194d693b9b3157c1070a226af35c629837df145ea12ad52fa8eabd65b025a63c1fb0726207a58cdde8 +a5e8ff8748419466ff6df5d389125f3d46aedacf44eaf12cbfe2f68d218c7d5ab6de4a8279d13aecc25f3b1d98230894 +91560d54a9715cfda9cf7133ae51c432d0bf7fcbaeb468004994e6838bfc5ddcfa30e4e780667d0c4c0376780b083017 +ae8adb3309cc89d79a55ff74f129bb311fe4f5351a8b87600a87e0c3ba60825f71fccf67eadcf7e4b243c619417540fd +8d92cc1a6baa7bfa96fbce9940e7187b3d142f1888bdcb09bb5c8abf63355e9fb942ac4b4819d9be0e0e822d3e8e2e08 +a6e8b79fdd90c34735bb8fbef02165ccbe55ea726dc203b15e7a015bf311c9cac56efd84d221cc55eaa710ee749dbdfe +a409b151de37bddf39ce5f8aa3def60ee91d6f03ddd533fce9bf7bdbeac618cc982c4f1ffbf6e302b8353d8f28f8c479 +b9693975ef82171b3b9fc318ca296e4fe6110b26cbdfd653418f7754563fa7b6e22d64f8025ee4243483fa321572bfe4 +a039ebe0d9ee4a03ade08e2104ffd7169975b224061924cca2aae71464d250851e9f5f6f6cb288b5bf15df9e252712a6 +b27834db422395bd330e53736a001341ce02c9b148c277dabac67dc422741bfa983c28d47c27e8214cd861f2bad8c6f6 +a2bafaf4e2daf629fd27d7d5ac09fb5efc930ff2ae610f37519808683aa583fe1c6f37207daf73de1d8a164f79a0c981 +b856cee1cfcf5e50db9af4ab0aed3db2f43c936eaea369b5bba65582f61f383c285efbda97b1c068c5d230cbe94f7722 +a61ab205554c0550fa267e46a3d454cd1b0a631646b3df140623ff1bfffaa118e9abe6b62814968cc2a506e9c03ea9a0 +8c78edcd106377b9cbdfa2abd5278724aed0d9e4ae5869b5d2b568fdabb7804c953bae96294fcc70ef3cd52ba2cbe4ed +8570869a9bbf6cc84966545a36586a60be4d694839f367b73dfc40b5f623fc4e246b39b9a3090694aa2e17e652d07fd1 +a905b82c4da8d866a894da72315a95dc98faa3c7b3d809aef18f3b2be4801e736a1b79a406179e8cac8f74d27e71ac52 +a8eb8679ff1a64908515f6720ff69434cb33d63aeb22d565fde506618908b1d37585e3bd4d044fd0838b55787af06b42 +af4d86b2fbd1684a657dffe4210321a71e6ae560c144d44668d1f324dc9630e98348c3d444622a689327c1a59cc169dd +80359c6eab16954559ab0e6a1fee9a0526c45d3cae1a371159a2e3aa9b893afdc3a785c9559a5fd9cd8cd774234bf819 +8d4e5ff81eb5d17bbe8ae6416538ca51a9427ce142b311f5cbb14febbbbb9c1ffc6489fd625b9266264c366c12a9d997 +92e181c66489c5fa063ba2a1a354b6fd3439b8b4365a8c90e42e169bfaa1fb5766bf3e0fe804399d18bc8fbcafb5c3b1 +a9ddf229360a095393885083716cb69c819b2d7cfb100e459c2e6beb999ff04446d1e4a0534832ae3b178cbe29f4f1d3 +8e085ef7d919302a1cc797857b75cff194bdbc1c5216434fa808c3dea0cf666f39d9b00f6d12b409693d7a9bd50a912c +916dc4dc89e5e6acf69e4485a09fc66968f9b292eac61a146df1b750aa3da2425a0743d492179f90a543a0d4cd72c980 +b9cbf17e32c43d7863150d4811b974882da338cf0ed1313765b431b89457021dd1e421eeaa52840ef00551bb630962dc +a6fb875786daec1a91484481787093d8d691dd07e15c9c0c6ae0404bf9dc26083ed15d03c6d3fe03e29f28e20da21269 +a870fcb54b9a029e8086de9b08da8782c64ad2cc2e7fdf955b913d294038bb8136193256b85267e75a4ca205808a76b4 +99883f057e09b88bf0e316f9814c091837fd5c26eeb16fec108c9fed4b7a2bd1c783dac0e4242b5a906621ab606c1e50 +85d89069ca3190577dab39bbec43c16bf6dbca439ad3eebd8f5e9f507d84c3c43e77fd6323224582566a3aa2c8018951 +9363ba219e0003f6e8a9d8937b9e1449e4b2c5cd57194563b758bea39deab88778e8f8e4f7816970a617fb077e1e1d42 +820622f25553c035326145c1d2d537dc9cfd064c2f5bdf6d4ec97814de5fe9a0fbd443345fa2ea0a9d40d81d3936aa56 +87e31110aaf447e70c3316459250e4f7f8c24420c97828f9eb33b22107542c5535bdb48b0e58682dd842edea2886ff08 +95bf80cac6f42029d843d1246588acb40a74802f9e94b2bf69b1833936767e701ef7b0e099e22ab9f20f8c0c4a794b6c +a46ecf612b2763d099b27fb814bd8fdbaee51d6b9ac277ad6f28350b843ce91d701371adfaaf4509400dc11628089b58 +8604decf299fb17e073969708be5befeb1090ab688ad9f3f97a0847a40ea9a11bbcfc7a91e8dc27bc67a155123f3bd02 +8eb765c8dc509061825f3688cb2d78b6fef90cf44db33783d256f09be284bc7282205279725b78882688a514247c4976 +b5c30b2244fa109d66b3a5270b178960fdec47d31e63db0b374b80d2b626409eb76d2e8d1ebf47ef96c166743032fc5e +aab01e76290a7e936989530221646160bf8f64e61e79282e980c8c5dcaaa805ff096efd01d075a2c75917a3f4bf15041 +b9d79671debd0b83d0c7c7c3e64c0fb1274300564b262771f839b49218501e7f38ef80cae1f7e5a3c34acdc74c89dab6 +92c0eaceadf036b3b9dfd2712013aba3dd7c30b7760f501f52141618265baa31840fe77850a7014dc528f71f8cf39ce6 +b3cdd098059980455dd5b1c04182df1bd12fa844a866f02a9f8a86aab95b59945baa9af99f687410bffc5b07153cb23c +b361b73a62f71256b7f6ea8e0f6615e14fc5a06ee98b928ab3c9dd3eef9d9d30070e9855c82b7facb639cacb3401e01f +b9c85fc0f25a3271cf28b1ca900078eaaa66cbab0a3e677606e898ac32781a2dfce4d9cbd07404599e2c3c02fa161c9d +ac5b4fdac2a0b2e6430d9fc72bde4249d72183b197fc7347bb1546ae6f544426686bbe0caec3ee973b6836da5e831c44 +b675aebf24b92e398e166f171a6df442b3f5919b6bee192f31675a5e8eeb77d34c6590a6f0c0857417e0f78cfb085db8 +a9bef942044d8d62e6a40169f7dc7b49e40cd0d77f8678dd7c7bae6f46c46786f9b1e319a3fa408f22a54fd2a4d70804 +a20d19cd917d5102ae9ca0cf532127d2b953aa3303310e8a8c4b3da025dded993a47e3a28e6b02acfadb6d65dc2d41a3 +a47fdb04059b83b2afb86a47b2368bbd7247c337a36d3333b6e5ef2cc9476a92c4907e4c58a845c9ef9b497621e0b714 +94a9e9ffc14b411e11a4ffa59878d59460263589003dc7b6915247c549f67feede279bf3645fdd92379022fb21e3caeb +b92e1177dd9ecdaf1370c71b14954219cf0851f309bc216d5907a4e2e84e0df3457018224150c142cc6bf86644bb4b73 +8bc57fadd68a265b7df9b42227a9c0968db7b1bb50dc12f7d755505779f1ff2c408672b3091e903366acc9ce15d19fb6 +b6b5efbe1ac4e1bd2e8447c45000d09397b772ca5496acc447b881022608a41c4f60388814607a01890190105bee7be3 +95f7c85fd614df968f8ccf8d086579c9e1cec4644ecf06da26e3511cb39635a7326b3cec47bd51cf5646f1c660425e9c +b81765fb319bcdc74b4d608383ccb4af7dd84413b23af637be12e2827a75f7e4bcd14441cf979ed9038ae366fbb6f022 +a120ea76cda8c6c50c97035078f6648afe6537809bdba26e7c9e61de8f3070d2347160f9d34010effbf2ec7e94f5749f +92c1b8631953b40d3cc77eee2c72a064b999c09a9b92c11d8fa7b4072966273901c9dba25f9f79f384d9f11a56f3fc7a +a4b00dc0ab67b2300abc9c516e34daf444d6497b066a90cfe3381ed2812304ed37b14f3b948990443dc6c1cf1bed460c +a9e9f7e13c9f031bc7b9e6f1417c7abcc38894fe7d3f54869ee277afd2efa3e6fb50757dd36c8c94d591e0abdea322cc +84f3e98f831792b5ad14bcfe62a4c9f296476c6087c4c1ec7767fc642fbca141ff6a3deeb8b4d4106a9cda5a9937eea0 +8eb1a7931bbea9a714226fd74b0100ab88355287d9b0a349c095e9b5809b98f237ffd706bce7d67a770da355fb9cec7b +9738ef8739e1742c1f26b51a1621be0b89d37406a370c531e236f635c7064c661818817bb3858908986aa687b28b21be +a9cf3ce8501b003ccaf57552a4c4ec31081e44526d3aa3791d3dc4a7e438a357c0956f93c500356186d8fd4588ffac5e +a7af6a219cca59225839a9de5b19263cb23d75557d448bc7d677b62591a2e068c45e5f4457cceb3e9efa01d0601fc18a +972a24ece5eda7692cbb6fb727f92740451bc1281835e2a02931b2b05824a16b01dbe5edd03a0ed5b441ff25a5cc0188 +b21d1ec7597ce95a42f759c9a8d79c8275d7e29047a22e08150f0f65014702f10b7edce8c03f6e7ab578ce8c3b0ec665 +a13a1c7df341bd689e1f8116b7afc149c1ef39161e778aa7903e3df2569356ad31834fa58ceb191485585ce5ef6835c3 +a57bdb08119dc3bc089b5b2b5383455c4de0c2fcdac2dcfa21c7ac5071a61635ff83eceb7412f53fab42d1a01991de32 +b2968748fa4a6921ee752d97aa225d289f599a7db7a222450e69706533573ded450380c87f8cdd4a8b8c8db1b42b5c97 +8718ec04e0d5f38e3034ecd2f13dfde840add500f43a5e13457a1c73db0d18138f938690c8c315b5bcbeb51e8b9a2781 +82094789e26c4a04f2f30bdb97b9aecca9b756cbd28d22ab3c8bed8afc5b2963340ddfc5a5f505e679bf058cbc5dcbb8 +a35b8a566dd6ab67eddc2467906bffc76c345d508e52e9e4bb407b4f2b2c5f39b31d5a4bf5022f87bf7181dc6be2fe41 +a8c93b1e893d4777c0e3a1b4bef3be90c215781501407c4011457fc3240e13524b4d2bea64a6d0a3efe3f3b0dae9b8ab +877095ad18b1e5870818f7a606127ba1736a0b55b0dbcd281ec307c84b08afc0c9117e3a880fe48bfc225fbf37671a97 +84405ee0421ed2db1add3593df8426a9c1fcc8063e875f5311a917febc193748678dd63171d0c21665fb68b6d786c378 +a52cdc8209c3c310bed15a5db260c4f4d4857f19c10e4c4a4cfe9dfc324dfac851421bb801509cf8147f65068d21603c +8f8a028a70dda7285b664722387666274db92230b09b0672f1ead0d778cee79aae60688c3dfd3a8ed1efdeda5784c9d4 +a0be42fecc86f245a45a8ed132d6efc4a0c4e404e1880d14601f5dce3f1c087d8480bad850d18b61629cf0d7b98e0ae0 +83d157445fc45cb963b063f11085746e93ab40ece64648d3d05e33e686770c035022c14fdf3024b32b321abf498689ad +8a72bbf5a732e2d4f02e05f311027c509f228aef3561fc5edac3ef4f93313845d3a9f43c69f42e36f508efcc64a20be0 +b9ca29b0ec8e41c6a02f54d8c16aebf377982488cbe2ed1753090f2db4f804f6269af03e015d647a82ef06ffaa8cba6c +b4df3858d61bbb5ded1cf0be22a79df65ae956e961fbb56c883e1881c4c21fe642e3f5a0c108a882e553ac59595e3241 +86457d8890ac8858d7bab180ef66851247c2bf5e52bf69a4051d1d015252c389684fcc30bb4b664d42fbf670574ab3a3 +86d5576ea6dfa06d9ebce4cd885450f270c88a283e1e0d29cab27851c14ed2f00355e167b52e1539f1218ad11d8f13dd +883ad1364dc2a92388bfafaa9bc943c55b2f813525831e817a6208c666829a40455dde494eba054b2495a95f7ce69e8a +8942371e6925231c2c603b5f5a882d8404d39f0c7c4232557c2610b21c2c07f145466da798ea78b7932da2b774aa3128 +a799eb71496783cc7faf12c9d9804bf6180699a004b2f07fc5cc36840f63ce7eee7dde9275819a9aa3f8d92dc0d47557 +8eb3fb5c769548ee38c7882f51b959c5d5a42b5935269ccf987d6ddbb25a206e80c6000bcc328af149e0727c0b7c02c0 +8f3910d64e421a8f2d8db4c7b352ba5b3fc519d5663973fea5962efe4364fb74448770df944ef37ffe0382648fb56946 +b41413e0c26ff124cf334dab0dc8e538293d8d519d11cc2d10895a96b2064ac60c7da39f08589b38726cffa4c3f0bfef +b46ef2eb10abae0f35fa4c9c7ee2665e8044b8d9f91988a241da40fd5bbc63166925582151941b400006e28bbc5ba22a +b8baa8b4c420bb572a3b6b85479b67d994c49a7ebfe1274687d946a0d0b36dfed7630cfb897350fa166f5e2eff8f9809 +964b46d359c687e0dcfbdab0c2797fc2bd1042af79b7418795b43d32ffca4de89358cee97b9b30401392ff54c7834f9f +8410d0203d382ebf07f200fd02c89b80676957b31d561b76563e4412bebce42ca7cafe795039f46baf5e701171360a85 +b1a8d5d473c1a912ed88ea5cfa37c2aea5c459967546d8f2f5177e04e0813b8d875b525a79c29cb3009c20e7e7292626 +afaab9a1637429251d075e0ba883380043eaf668e001f16d36737028fded6faa6eeed6b5bb340f710961cee1f8801c41 +aef17650003b5185d28d1e2306b2f304279da50925f2704a6a3a68312f29fe5c2f2939f14e08b0ba9dee06ea950ad001 +97bcc442f370804aa4c48c2f8318d6f3452da8389af9335e187482d2e2b83b9382e5c297dce1a0f02935e227b74e09a3 +8a67a27b199f0bcd02d52a3e32f9b76a486b830ec481a49a4e11807e98408b7052b48581b5dd9f0b3e93052ec45dfb68 +b113bf15f430923c9805a5df2709082ab92dcdf686431bbad8c5888ca71cc749290fa4d4388a955c6d6ee3a3b9bc3c53 +8629ca24440740ce86c212afed406026f4ea077e7aa369c4151b6fa57bca7f33f9d026900e5e6e681ae669fd2bd6c186 +933a528371dcecc1ec6ded66b1c7b516bd691b3b8f127c13f948bfbcda3f2c774c7e4a8fbee72139c152064232103bdf +8568ddd01f81a4df34e5fa69c7f4bb8c3c04274147498156aec2e3bd98ea3e57c8a23503925de8fa3de4184563a2b79e +8160874ec030f30fda8f55bcf62613994ff7ed831e4901c7560eac647182b4a9b43bfaff74b916602b9d6ae3bfcaf929 +ae71c48d48cf9459800cdf9f8e96bc22e2d4e37259e5c92a2b24fbe2c6ca42675e312288603c81762f6ceb15400bc4c9 +b05f39bb83fda73e0559db1fd4a71423938a87ad9f060d616d4f4a6c64bf99472a2cbfb95f88b9257c9630fc21a0b81f +80c8479a640ed7a39e67f2db5ad8dfd28979f5443e8e6c23da8087fc24134d4b9e7c94320ffa4154163270f621188c27 +9969ba20ee29c64cb3285a3433a7e56a0fe4ddc6f3d93e147f49fe021bed4a9315266ebb2fb0eb3036bb02001ae015e6 +a198c89fef2ab88e498703b9021becc940a80e32eb897563d65db57cc714eaa0e79092b09dd3a84cfab199250186edcc +8df14a3db8fe558a54d6120bad87405ba9415a92b08c498812c20416c291b09fed33d1e2fcf698eb14471f451e396089 +81e245ef2649b8a5c8d4b27188dd7e985ef6639090bdc03462c081396cf7fc86ed7d01bfe7e649d2b399255e842bdc21 +8659f622c7ab7b40061bcf7a10144b51ad3ab5348567195924f2944e8c4ce137a37f1ba328e4716c10806f3fb7271689 +a575d610fc8fe09334ca619ecdadf02d468ca71dd158a5a913252ca55ea8d8f9ce4548937c239b9cb8ab752a4d5af24a +94744549cd9f29d99f4c8c663997bdfa90e975b31f1086214245de9c87b0c32209f515a0de64d72d5ef49c09b0a031fa +80a8677862b056df59e350c967a27436c671b65d58854e100115bac9824ba177e94c2a1bfcaa191a071b9cefdbee3989 +91be9a5504ec99922440f92a43fe97ddce2f21b9d94cd3a94c085a89b70c903696cec203bbab6d0a70693ba4e558fb01 +8c5a0087bcd370734d12d9b3ab7bc19e9a336d4b49fc42825b2bfedcd73bb85eb47bf8bb8552b9097cc0790e8134d08c +933aa9e6bd86df5d043e0577a48e17eea3352e23befdbb7d7dcac33b5703d5ace230443ac0a40e23bf95da4cc2313478 +984b7ee4bd081ee06c484db6114c2ce0ba356988efb90f4c46ff85ed2865fb37f56a730166c29ef0ae3345a39cdeae7a +ae830f908ea60276c6c949fb8813e2386cf8d1df26dcf8206aa8c849e4467243e074471380ed433465dc8925c138ea4c +874c1df98d45b510b4f22feff46a7e8ed22cfc3fad2ac4094b53b9e6477c8dfc604976ca3cee16c07906dece471aa6c6 +a603eb60d4c0fb90fa000d2913689126849c0261e6a8649218270e22a994902965a4e7f8c9462447259495fe17296093 +a7c73d759a8ad5e3a64c6d050740d444e8d6b6c9ade6fb31cb660fa93dc4a79091230baccb51c888da05c28cb26f6f3f +a4411b79b6a85c79ea173bd9c23d49d19e736475f3d7d53213c5349ebb94a266d510d12ba52b2ac7a62deaaaec7339b8 +943b84f8bbcee53b06266b5c4cd24d649d972593837fe82b0bf5d5e1bbc1a2bf148e1426c366d7c39ab566b10224cadc +8300012096a8b4cefecc080054bf3ceb0918162ba263c6848860423407796b5eb517170c0bad8e4905ac69a383055a21 +8244a1e3ad41908c6f037e2f8db052e81f281646141334829f36c707f307448b9ab79a7f382a1e8d86f877c90b59271c +8eca1b74687802ecc36a5d39e4516a9dee3de61a2047252d9ed737b49e0090c386e9d792ac004c96337681c7f29a16ad +b70fa47535f0524835039a20036c61e77f66146ad79d3d339214d8744742db41ceeb577c829d000011aeafbb12e09579 +84b3abbce48689f3adbb99889c7fd1f3e15ab455d477e34f5151c5c1c358ed77a5b6a581879f7e0f1f34106e0792e547 +ab45ecb58c0ef0dbce3d16afc6ac281e0d90ec48741ea96a141152647e98fcc87f3a3ff07ba81f3179118453ce123156 +90d231a145ba36a59087e259bbfc019fa369201fcfeaa4347d5fd0a22cd8a716e5a797f3cc357f2779edb08f3b666169 +a4f6074d23c6c97e00130bc05f25213ca4fa76c69ca1ace9dece904a2bdd9d987661f5d55023b50028c444af47ff7a08 +933af884939ad0241f3f1f8e8be65f91d77ac0fb234e1134d92713b7cfb927f1933f164aec39177daa13b39c1370fac8 +80d1db6933ce72091332ae47dc691acb2a9038f1239327b26d08ea9d40aa8f2e44410bbda64f2842a398cbe8f74f770f +a7a08605be2241ccc00151b00b3196d9c0717c4150909a2e9cd05538781231762b6cc6994bebbd4cddae7164d048e7b2 +96db0d839765a8fdbbac03430fa800519e11e06c9b402039e9ae8b6503840c7ecac44123df37e3d220ac03e77612f4e4 +96d70f8e9acd5a3151a8a9100ad94f16c289a31d61df681c23b17f21749c9062622d0a90f6d12c52397b609c6e997f76 +8cf8e22273f7459396ff674749ab7e24c94fe8ab36d45d8235e83be98d556f2b8668ba3a4ec1cb98fac3c0925335c295 +97b7e796a822262abc1a1f5a54cb72a1ea12c6c5824ac34cd1310be02d858a3c3aa56a80f340439b60d100e59c25097d +a48208328b08769737aa1a30482563a4a052aea736539eceab148fa6653a80cb6a80542e8b453f1f92a33d0480c20961 +b612184941413fd6c85ff6aa517b58303b9938958aa85a85911e53ed308778624d77eadb27ccf970573e25d3dfd83df7 +b3717068011648c7d03bbd1e2fc9521a86d2c3ae69113d732c2468880a3b932ebec93596957026477b02842ed71a331b +a0ad363e1352dcf035b03830fef4e27d5fd6481d29d5e8c9d51e851e3862d63cdcbaf8e330d61c1b90886921dac2c6fd +8db409fdacfa4bfdaf01cc87c8e97b53ca3a6e3a526d794eaad1c2023f3df4b888f1bf19fee9a990fe6d5c7c3063f30c +b34d6975310ab15938b75ef15020a165fc849949065d32d912554b51ffa1d3f428a6d1a396cb9329367670391de33842 +9117285e9e6762853fc074b8a92b3923864de2c88c13cea7bab574aaf8cdd324843455d2c3f83c00f91f27c7ecc5592a +b4b2e8f190ea0b60819894710c866bf8578dd1b231ae701d430797cc7ede6e216e8ca6a304f3af9484061563645bf2ab +8c493c6853ab135d96a464815dd06cad8b3e8b163849cdefc23d1f20211685753b3d3e147be43e61e92e35d35a0a0697 +9864d7880f778c42d33cf102c425e380d999d55a975a29c2774cad920dfddb80087a446c4f32ed9a6ab5f22ec6f82af0 +90f67fe26f11ca13e0c72b2c2798c0d0569ed6bc4ce5bbaf517c096e7296d5dd5685a25012f6c6d579af5b4f5d400b37 +a228872348966f26e28a962af32e8fa7388d04bc07cfc0224a12be10757ac7ab16a3387c0b8318fcb0c67384b0e8c1a4 +a9d9d64bba3c03b51acf70aeb746a2712ddafe3b3667ae3c25622df377c2b5504e7ab598263bec835ab972283c9a168b +932128971c9d333f32939a1b46c4f7cf7e9d8417bd08dc5bd4573ccbd6ec5b460ac8880fb7f142f7ef8a40eef76d0c6d +964115e7838f2f197d6f09c06fbb2301d6e27c0ecdf208350cf3b36c748436dac50f47f9f9ac651c09ab7ad7221c7e43 +a5941f619e5f55a9cf6e7f1499b1f1bcddcc7cf5e274efedaaad73a75bc71b1fc5c29cd903f6c69dc9a366a6933ca9d1 +a154bf5eaec096029e5fe7c8bf6c695ae51ace356bb1ad234747776c7e1b406dee2d58864c3f4af84ed69f310974125e +b504e6209d48b0338ab1e4bdab663bac343bb6e0433466b70e49dc4464c1ec05f4a98111fd4450393607510ae467c915 +813411918ea79bdde295393284dc378b9bdc6cfcb34678b9733ea8c041ac9a32c1e7906e814887469f2c1e39287e80f8 +8be0369f94e4d72c561e6edb891755368660208853988647c55a8eed60275f2dd6ee27db976de6ecf54ac5c66aaf0ae6 +a7e2701e55b1e7ea9294994c8ad1c080db06a6fc8710cd0c9f804195dce2a97661c566089c80652f27b39018f774f85e +956b537703133b6ddf620d873eac67af058805a8cc4beb70f9c16c6787bf3cc9765e430d57a84a4c3c9fbdd11a007257 +835ae5b3bb3ee5e52e048626e3ddaa49e28a65cb94b7ecdc2e272ff603b7058f1f90b4c75b4b9558f23851f1a5547a35 +85d67c371d1bf6dc72cca7887fa7c886ce988b5d77dc176d767be3205e80f6af2204d6530f7060b1f65d360a0eaeff30 +a84a6647a10fcef8353769ef5f55a701c53870054691a6e9d7e748cbe417b3b41dbb881bae67adc12cb6596c0d8be376 +87ffe271fc0964cb225551c7a61008d8bcb8b3d3942970dbcc2b9f4f9045a767971880368ea254e2038a3a0b94ecf236 +964bb721c51d43ee7dd67c1a2b7dd2cc672ce8fad78c22dcddb43e6aab48d9a4a7dc595d702aa54a6fb0ffabf01f2780 +a89b3f84bb7dcbe3741749776f5b78a269f6b1bebb8e95d3cc80b834fd2177c6be058d16cacfd0d5e1e35e85cde8b811 +b4314538e003a1587b5592ff07355ea03239f17e75c49d51f32babe8e048b90b046a73357bcb9ce382d3e8fbe2f8e68b +86daf4bf201ae5537b5d4f4d734ed2934b9cf74de30513e3280402078f1787871b6973aa60f75858bdf696f19935a0e2 +b1adf5d4f83f089dc4f5dae9dbd215322fa98c964e2eaa409bf8ca3fa5c627880a014ed209492c3894b3df1c117236c4 +b508d52382c5bac5749bc8c89f70c650bb2ed3ef9dc99619468c387c1b6c9ff530a906dfa393f78f34c4f2f31478508a +a8349a5865cb1f191bebb845dfbc25c747681d769dbffd40d8cedf9c9a62fa2cbc14b64bb6121120dab4e24bef8e6b37 +af0500d4af99c83db8890a25f0be1de267a382ec5e9835e2f3503e1bac9412acf9ff83a7b9385708ef8187a38a37bc77 +b76d57a1c1f85b8a8e1722a47057b4c572800957a6b48882d1fc21309c2e45f648a8db0fcff760d1dbc7732cf37c009b +b93c996cec0d3714667b5a5a5f7c05a7dc00bbc9f95ac8e310626b9e41ae4cc5707fac3e5bd86e1e1f2f6d9627b0da94 +93216fdb864217b4c761090a0921cf8d42649ab7c4da1e009ec5450432564cb5a06cb6e8678579202d3985bd9e941cef +8b8be41105186a339987ae3a5f075fbc91f34b9984d222dfed0f0f85d2f684b56a56ab5dc812a411570491743d6c8b18 +959b72782a6b2469e77fe4d492674cc51db148119b0671bd5d1765715f49fa8a87e907646671161586e84979ef16d631 +86b7fc72fb7e7904ea71d5e66ba0d5d898ace7850985c8cc4a1c4902c5bf94351d23ce62eed45e24321fb02adfa49fc8 +a2f244e7c9aa272cb0d067d81d25e5a3045b80b5a520b49fd5996ece267a7f1bea42e53147bbf153d9af215ea605fc9e +81aa2efa5520eebc894ce909ba5ce3250f2d96baa5f4f186a0637a1eea0080dd3a96c2f9fadf92262c1c5566ddb79bab +b607dd110cfe510d087bcff9a18480ba2912662256d0ab7b1d8120b22db4ad036b2266f46152754664c4e08d0fc583f6 +8f588d5f4837e41312744caac5eee9ddc3ad7085871041694f0b5813edf83dc13af7970f7c9b6d234a886e07fa676a04 +924921b903207783b31016cbec4e6c99e70f5244e775755c90d03a8b769738be3ba61577aca70f706a9c2b80040c9485 +ae0a42a222f1a71cd0d3c69ffb2f04c13e1940cce8efabe032629f650be3ceed6abb79651dbb81cb39a33286eb517639 +a07d7d76460f31f5f0e32e40a5ea908d9d2aebf111ac4fadee67ef6540b916733c35a777dcdc05f6417726ca1f2d57dd +88d7f8a31f8c99794291847d28745e5d0b5d3b9684ca4170b686ffbb5bb521a3ef6746c3c8db22e4250a0cdff7939d96 +849573071fd98c020dc9a8622a9eff221cb9f889bde259e7127a8886b73bef7ad430b87750915658918dcfb6b7b4d8d3 +b12d59f732fa47fad175d6263734da8db89230fd340a46ad1cdee51e577041a5c80bf24cd195593e637daf1a66ef5a98 +abbcfb8a4a6d5e269ee1ac5e277df84416c73ca55ec88317f73608201af25af0cb65b943c54684a5651df3a26e3daca2 +ab157f589bdbaf067a6a7ba7513df0492933855d39f3a081196cf2352e0ddc0162d476c433320366e3df601e0556278d +a86c0619b92e5ae4f7daa876a2abc5ba189156afc2fa05eef464dfa342ba37fc670d0dc308ad3822fcb461ab001bac30 +a3f292946476cfe8d5e544a5325439a00e0165a5f9bf3bb6a53f477baeac7697cc0377745536681aa116f326ce911390 +8aecbbfd442a6a0f01c1c09db5d9d50213eb6f1ff6fab674cde3da06a4edff3ed317e804f78300c22ef70c336123e05d +834ed4b58211fcd647d7bf7c0a3ba9085184c5c856b085e8a0fcd5215c661ef43d36f3f0f6329a9f1370501b4e73b6e4 +a114ea5ad2b402a0de6105e5730907f2f1e458d28ae35144cf49836e0ad21325fe3e755cfb67984ae0a32e65402aad1e +a005f12bed97d71cee288b59afe9affb4d256888727343944a99913980df2c963fe02f218e6ea992f88db693a4498066 +a010f286ab06b966e3b91ff8f1bdbe2fe9ab41a27bc392d5787aa02a46e5080e58c62c7d907818caae9f6a8b8123e381 +857bd6df2ddef04dbc7c4f923e0b1696d3016c8bfed07fdfa28a3a3bd62d89b0f9df49aae81cbb6883d5e7b4fadae280 +b3927030da445bc4756ac7230a5d87412a4f7510581fb422212ce2e8cf49689aca7ba71678743af06d4de4914c5aa4a0 +b86403182c98fcce558d995f86752af316b3b2d53ba32075f71c7da2596747b7284c34a1a87de604fcc71e7e117a8add +98dd19b5527733041689b2a4568edaf6aa0fe1a3dd800c290cda157b171e053648a5772c5d3d4c80e5a795bc49adf12e +88a3c227bb7c9bff383f9ad3f7762245939a718ab85ae6e5e13180b12bf724d42054d3852b421c1cd1b3670baddecb63 +b3cfd9ad66b52bbe57b5fff0fad723434d23761409b92c4893124a574acc1e6b1e14b4ec507661551cbbe05e16db362e +923e1bb482cf421dd77801f9780f49c3672b88508a389b94015fd907888dc647ee9ea8ec8d97131d235d066daf1f42b7 +8d5e16240f04f92aa948181d421006bdbc7b215648fb6554193224d00cf337ebbb958f7548cf01b4d828acffb9fbc452 +8b2b8f18ad0559746f6cda3acca294a1467fb1a3bc6b6371bc3a61a3bfe59418934fa8706f78b56005d85d9cb7f90454 +a9316e2a94d6e31426d2ae7312878ba6baaac40f43e2b8a2fa3ab5a774c6918551554b2dbb23dc82f70ba3e0f60b5b0d +9593116d92cf06b8cd6905a2ce569ee6e69a506c897911f43ae80fc66c4914da209fc9347962034eebbc6e3e0fe59517 +887d89d2b2d3c82b30e8f0acf15f0335532bd598b1861755498610cb2dd41ff5376b2a0bb757cb477add0ce8cfe7a9fc +b514cfe17875ecb790ad055271cc240ea4bda39b6cfa6a212908849c0875cb10c3a07826550b24c4b94ea68c6bb9e614 +a563d5187966d1257d2ed71d53c945308f709bcc98e3b13a2a07a1933dc17bcb34b30796bd68c156d91811fbd49da2cb +a7195ccc53b58e65d1088868aeeb9ee208103e8197ad4c317235bb2d0ad3dc56cb7d9a7186416e0b23c226078095d44c +a838e7a368e75b73b5c50fbfedde3481d82c977c3d5a95892ac1b1a3ea6234b3344ad9d9544b5a532ccdef166e861011 +9468ed6942e6b117d76d12d3a36138f5e5fb46e3b87cf6bb830c9b67d73e8176a1511780f55570f52d8cdb51dcf38e8c +8d2fc1899bc3483a77298de0e033085b195caf0e91c8be209fd4f27b60029cbe1f9a801fbd0458b4a686609762108560 +8f4e44f8ca752a56aa96f3602e9234ad905ad9582111daf96a8c4d6f203bf3948f7ce467c555360ad58376ee8effd2ba +8fb88640b656e8f1c7c966c729eb2ba5ccf780c49873f8b873c6971840db7d986bdf1332ba80f8a0bb4b4ee7401468fa +b72aa3235868186913fb5f1d324e748cd3ce1a17d3d6e6ea7639a5076430fe0b08841c95feb19bb94181fe59c483a9eb +b8b102690ebb94fc4148742e7e3fd00f807b745b02cbe92cd92992c9143b6db7bb23a70da64a8b2233e4a6e572fc2054 +8c9ae291f6cd744e2c6afe0719a7fc3e18d79307f781921fb848a0bf222e233879c1eca8236b4b1be217f9440859b6ce +a658ede47e14b3aad789e07f5374402f60e9cacb56b1b57a7c6044ca2418b82c98874e5c8c461898ebd69e38fecd5770 +89c0cb423580e333923eb66bda690f5aca6ec6cba2f92850e54afd882ba608465a7dbb5aa077cd0ca65d9d00909348ab +aed8e28d98d5508bd3818804cf20d296fe050b023db2ed32306f19a7a3f51c7aaafed9d0847a3d2cd5ba5b4dabbc5401 +96a0fcd6235f87568d24fb57269a94402c23d4aa5602572ad361f3f915a5f01be4e6945d576d51be0d37c24b8b0f3d72 +935d0c69edd5dfa8ed07c49661b3e725b50588f814eb38ea31bcc1d36b262fae40d038a90feff42329930f8310348a50 +900518288aa8ea824c7042f76710f2ea358c8bb7657f518a6e13de9123be891fa847c61569035df64605a459dad2ecc8 +947d743a570e84831b4fb5e786024bd752630429d0673bf12028eb4642beb452e133214aff1cfa578a8856c5ebcb1758 +a787266f34d48c13a01b44e02f34a0369c36f7ec0aae3ec92d27a5f4a15b3f7be9b30b8d9dd1217d4eeedff5fd71b2e5 +a24b797214707ccc9e7a7153e94521900c01a1acd7359d4c74b343bfa11ea2cdf96f149802f4669312cd58d5ab159c93 +97f5ee9c743b6845f15c7f0951221468b40e1edaef06328653a0882793f91e8146c26ac76dd613038c5fdcf5448e2948 +80abd843693aed1949b4ea93e0188e281334163a1de150c080e56ca1f655c53eb4e5d65a67bc3fc546ed4445a3c71d00 +908e499eb3d44836808dacff2f6815f883aeced9460913cf8f2fbbb8fe8f5428c6fc9875f60b9996445a032fd514c70f +ae1828ef674730066dc83da8d4dd5fa76fc6eb6fa2f9d91e3a6d03a9e61d7c3a74619f4483fe14cddf31941e5f65420a +a9f4dbe658cd213d77642e4d11385a8f432245b098fccd23587d7b168dbeebe1cca4f37ee8d1725adb0d60af85f8c12f +93e20ee8a314b7772b2439be9d15d0bf30cd612719b64aa2b4c3db48e6df46cea0a22db08ca65a36299a48d547e826a7 +a8746a3e24b08dffa57ae78e53825a9ddbbe12af6e675269d48bff4720babdc24f907fde5f1880a6b31c5d5a51fbb00e +b5e94dfab3c2f5d3aea74a098546aa6a465aa1e3f5989377d0759d1899babf543ad688bb84811d3e891c8713c45886c5 +a3929bada828bd0a72cda8417b0d057ecb2ddd8454086de235540a756e8032f2f47f52001eb1d7b1355339a128f0a53b +b684231711a1612866af1f0b7a9a185a3f8a9dac8bde75c101f3a1022947ceddc472beb95db9d9d42d9f6ccef315edbc +af7809309edbb8eb61ef9e4b62f02a474c04c7c1ffa89543d8c6bf2e4c3d3e5ecbd39ec2fc1a4943a3949b8a09d315a6 +b6f6e224247d9528ef0da4ad9700bee6e040bbf63e4d4c4b5989d0b29a0c17f7b003c60f74332fefa3c8ddbd83cd95c1 +adbcec190a6ac2ddd7c59c6933e5b4e8507ce5fd4e230effc0bd0892fc00e6ac1369a2115f3398dfc074987b3b005c77 +8a735b1bd7f2246d3fa1b729aecf2b1df8e8c3f86220a3a265c23444bdf540d9d6fe9b18ed8e6211fad2e1f25d23dd57 +96b1bf31f46766738c0c687af3893d098d4b798237524cb2c867ed3671775651d5852da6803d0ea7356a6546aa9b33f2 +8036e4c2b4576c9dcf98b810b5739051de4b5dde1e3e734a8e84ab52bc043e2e246a7f6046b07a9a95d8523ec5f7b851 +8a4f4c32ee2203618af3bb603bf10245be0f57f1cfec71037d327fa11c1283b833819cb83b6b522252c39de3ce599fa5 +ad06ed0742c9838e3abaaffdb0ac0a64bad85b058b5be150e4d97d0346ed64fd6e761018d51d4498599669e25a6e3148 +8d91cb427db262b6f912c693db3d0939b5df16bf7d2ab6a7e1bc47f5384371747db89c161b78ff9587259fdb3a49ad91 +ae0a3f84b5acb54729bcd7ef0fbfdcf9ed52da595636777897268d66db3de3f16a9cf237c9f8f6028412d37f73f2dfad +8f774109272dc387de0ca26f434e26bc5584754e71413e35fa4d517ee0f6e845b83d4f503f777fe31c9ec05796b3b4bc +a8670e0db2c537ad387cf8d75c6e42724fae0f16eca8b34018a59a6d539d3c0581e1066053a2ec8a5280ffabad2ca51f +ac4929ed4ecad8124f2a2a482ec72e0ef86d6a4c64ac330dab25d61d1a71e1ee1009d196586ce46293355146086cabba +845d222cb018207976cc2975a9aa3543e46c861486136d57952494eb18029a1ebb0d08b6d7c67c0f37ee82a5c754f26f +b99fa4a29090eac44299f0e4b5a1582eb89b26ed2d4988b36338b9f073851d024b4201cd39a2b176d324f12903c38bee +9138823bc45640b8f77a6464c171af2fe1700bdc2b7b88f4d66b1370b3eafe12f5fbb7b528a7e1d55d9a70ca2f9fc8e6 +8ac387dc4cf52bc48a240f2965ab2531ae3b518d4d1f99c0f520a3d6eb3d5123a35ef96bed8fa71ee2f46793fa5b33b3 +864adec6339d4c2ba2525621fceabd4c455902f6f690f31a26e55413e0722e5711c509dc47ce0bcc27bbdc7651768d2d +a0a52edb72268a15201a968dabc26a22909620bda824bd548fb8c26cc848f704166ed730d958f0173bd3b0a672f367bd +949e445b0459983abd399571a1a7150aab3dd79f4b52a1cd5d733e436c71c1d4b74287c6b0ce6cc90c6711ba4c541586 +858966355dac11369e3b6552f2b381665181693d5a32e596984da3314021710b25a37d8c548b08700eea13d86cb22f21 +974bcbb8d38c5e6518745cc03ad436e585b61f31d705e7e2e5085da9655d768ac4d800904f892c3dab65d6223e3f1fd6 +8092b6506b01308bf6187fde5ebd4fa7448c9a640961ba231be22ac5fa2c7635ef01e8b357722c7695d09b723101ea2a +a5b8ef360bf28533ee17d8cd131fff661d265f609db49599085c0c7d83b0af409a1b5c28e3a5e5d7f8459a368aa121e8 +b031b6d5e3ceab0f0c93314b3b675f55cf18cbc86f70444af266fe39cb22fd7dad75d8c84e07f1c1bfa2cb8283e1361a +93ad489e4f74658320c1cceed0137c023d3001a2c930ed87e6a21dbf02f2eb6ad1c1d8bcb3739c85dcfbecb040928707 +b15e4ec2cdab0d34aec8d6c50338812eb6ecd588cf123a3e9d22a7ca23b5a98662af18289f09e6cdd85a39a2863c945c +b304f71a9717cf40c22073f942618b44bf27cd5e2ed4a386ad45d75b0fcb5a8dafd35158211eaf639495c6f1a651cedb +b82d78d3eaaa7c5101b7a5aae02bd4f002cd5802d18c3abcda0dd53b036661c6d3c8b79e0abe591eab90b6fdc5fef5e3 +abbd1884243a35578b80914a5084449c237ee4e4660c279d1073a4d4217d1b55c6b7e9c087dfd08d94ac1416273d8d07 +92f4b61c62502745e3e198ec29bca2e18696c69dcb914d1f3a73f4998d012b90caf99df46e9bb59942e43cce377fe8fd +906e79df98185820c8208844e1ba6bd86cb96965814b01310bd62f22cbec9b5d379b2ef16772d6fc45a421b60cfd68fe +a0eae2784ef596e2eb270dd40c48d6c508e4394c7d6d08d4cc1b56fde42b604d10ba752b3a80f2c4a737e080ef51b44f +94c084985e276dc249b09029e49a4ef8a369cd1737b51c1772fbb458d61e3fe120d0f517976eba8ffa5711ba93e46976 +83619a0157eff3f480ab91d1d6225fead74c96a6fd685333f1e8e4d746f6273e226bad14232f1d1168a274e889f202f1 +a724fe6a83d05dbbf9bb3f626e96db2c10d6d5c650c0a909415fbda9b5711c8b26e377201fb9ce82e94fa2ab0bf99351 +a8a10c1b91a3a1fa2d7fd1f78a141191987270b13004600601d0f1f357042891010717319489f681aa8a1da79f7f00d5 +a398a2e95b944940b1f8a8e5d697c50e7aa03994a8a640dfad4ea65cfb199a4d97861a3ec62d1c7b2b8d6e26488ca909 +a2eedfe5452513b2a938fffd560798ef81379c5a5032d5b0da7b3bb812addbaad51f564c15d9acbbfc59bb7eddd0b798 +ab31c572f6f145a53e13b962f11320a1f4d411739c86c88989f8f21ab629639905b3eedb0628067942b0dc1814b678ca +ad032736dd0e25652d3566f6763b48b34ea1507922ed162890cd050b1125ec03b6d41d34fccba36ec90336f7cdf788ed +83028a558a5847293147c483b74173eca28578186137df220df747fccd7d769528d7277336ea03c5d9cdd0bc5ae3d666 +ab5d182cd1181de8e14d3ef615580217c165e470b7a094a276b78a3003089123db75c6e1650bf57d23e587c587cd7472 +a4793e089fbdb1597654f43b4f7e02d843d4ab99ee54099c3d9f0bd5c0c5657c90bb076379a055b00c01b12843415251 +98bdc52ee062035356fb2b5c3b41673198ddc60b2d1e546cb44e3bb36094ef3c9cf2e12bbc890feb7d9b15925439d1ea +a4f90cca6f48024a0341bd231797b03693b34e23d3e5b712eb24aba37a27827319b2c16188f97c0636a0c115381dc659 +8888e6c2e4a574d04ba5f4264e77abc24ccc195f1a7e3194169b8a2ceded493740c52db4f9833b3dbf4d67a3c5b252cb +83dc4e302b8b0a76dc0292366520b7d246d73c6aebe1bdd16a02f645c082197bcff24a4369deda60336172cefbcf09af +a4eb2741699febfeb793914da3054337cc05c6fa00d740e5f97cb749ae16802c6256c9d4f0f7297dcdbb8b9f22fc0afa +8b65557d5be273d1cb992a25cfce40d460c3f288d5cb0a54bdef25cbd17cdea5c32ec966e493addf5a74fd8e95b23e63 +97c6577e76c73837bcb398b947cb4d3323d511141e0ddd0b456f59fbb1e8f920a5c20d7827a24309145efddee786140f +abcc0849ffe2a6a72157de907907b0a52deece04cf8317bee6fe1d999444b96e461eac95b6afde3d4fe530344086a625 +9385c0115cb826a49df1917556efa47b5b5e4022b6a0d2082053d498ec9681da904ecf375368bb4e385833116ea61414 +8b868c1841f0cdc175c90a81e610b0652c181db06731f5c8e72f8fafa0191620742e61a00db8215a991d60567b6a81ca +a8df15406f31b8fcf81f8ff98c01f3df73bf9ec84544ddec396bdf7fafa6fe084b3237bf7ef08ad43b26517de8c3cd26 +a9943d21e35464ce54d4cc8b135731265a5d82f9ccf66133effa460ffdb443cdb694a25320506923eede88d972241bf2 +a1378ee107dd7a3abcf269fd828887c288363e9b9ca2711377f2e96d2ed5e7c5ec8d3f1da995a3dcbedf1752d9c088fc +8a230856f9227b834c75bdebc1a57c7298a8351874bf39805c3e0255d6fd0e846f7ad49709b65ec1fd1a309331a83935 +877bcf42549d42610e1780e721f5800972b51ba3b45c95c12b34cb35eeaf7eac8fa752edd7b342411820cf9093fea003 +84c7a0b63842e50905624f1d2662506b16d1f3ea201877dfc76c79181c338b498eceb7cad24c2142c08919120e62f915 +8e18b1bd04b1d65f6ed349b5d33a26fe349219043ead0e350b50ae7a65d6ff5f985dd9d318d3b807d29faa1a7de4fe42 +8ea7b5a7503e1f0b3c3cd01f8e50207044b0a9c50ed1697794048bbe8efd6659e65134d172fb22f95439e1644f662e23 +b1954a2818cad1dad6d343a7b23afa9aa8ad4463edc4eb51e26e087c2010927535020d045d97d44086d76acdb5818cbf +a5271ea85d0d21fa1ff59b027cf88847c0f999bbf578599083ff789a9b5228bc161e1c81deb97e74db1a82a0afd61c50 +aa2fa4c05af3387e2c799315781d1910f69977ec1cfea57a25f1a37c63c4daaa3f0ecd400884a1673e17dd5300853bcf +b1cd2a74ca0b8e6090da29787aef9b037b03b96607983a308b790133bd21297b21ca4e2edec890874096dbf54e9d04c3 +801931607ec66a81272feaa984f0b949ad12d75ecf324ba96627bd4dc5ddead8ebf088f78e836b6587c2b6c0b3366b6c +95d79504710bdf0ad9b9c3da79068c30665818c2f0cdbba02cc0a5e46e29d596032ac984441b429bd62e34535c8d55b0 +9857d41e25e67876510ff8dadf0162019590f902da1897da0ef6fc8556e3c98961edb1eb3a3a5c000f6c494413ded15e +8740c9ffe6bd179c19a400137c3bd3a593b85bd4c264e26b4dfb9e2e17ac73e5b52dfacc1dcb4033cfc0cd04785f4363 +977f98f29d948b4097a4abdf9345f4c1fb0aa94ba0c6bf6faa13b76f3a3efc8f688e1fe96099b71b3e1c05041118c8d1 +a364422b1239126e3e8d7b84953ce2181f9856319b0a29fcab81e17ac27d35798088859c1cfc9fc12b2dbbf54d4f70b3 +a0f6ba637f0db7a48e07439bb92ddb20d590ce9e2ed5bab08d73aa22d82c32a9a370fe934cbe9c08aeb84b11adcf2e0e +a2c548641bd5b677c7748327cca598a98a03a031945276be6d5c4357b6d04f8f40dd1c942ee6ec8499d56a1290ac134d +9863e9cc5fbcdbd105a41d9778d7c402686bfd2d81d9ed107b4fda15e728871c38647529693306855bee33a00d257a7e +a54173bf47b976290c88fd41f99300135de222f1f76293757a438450880e6f13dbde3d5fe7afc687bdfbcfc4fbc1fc47 +b8db413917c60907b73a997b5ab42939abd05552c56a13525e3253eb72b83f0d5cc52b695968a10005c2e2fe13290e61 +a1f8388ef21697c94ba90b1a1c157f0dc138e502379e6fc5dc47890d284563e5db7716266e1b91927e5adf3cde4c0a72 +9949013a59d890eb358eab12e623b2b5edb1acbee238dfad8b7253102abc6173922e188d5b89ec405aa377be8be5f16d +a00fdb7710db992041f6ddb3c00099e1ce311dea43c252c58f560c0d499983a89de67803a8e57baa01ee9d0ee6fa1e44 +a8b1bcbed1951c9cdb974b61078412881b830b48cd6b384db0c00fa68bcc3f4312f8e56c892ea99d3511857ef79d3db9 +8f3ee78404edc08af23b1a28c2012cee0bdf3599a6cb4ea689fc47df4a765ef519191819a72562b91a0fbcdb896a937e +8155bbb7fa8d386848b0a87caae4da3dec1f3dade95c750a64a8e3555166ccc8799f638bd80ed116c74e3a995541587a +abfe30adbc0a6f1fd95c630ed5dac891b85384fa9331e86b83217f29dff0bd7cad19d328485715a7e3df9a19069d4d2f +89d0783e496ee8dbb695764b87fb04cee14d4e96c4ba613a19736971c577d312079048142c12ce5b32b21e4d491d281b +856b8dbc9c5d8f56b6bb7d909f339ca6da9a8787bba91f09130a025ab6d29b64dbf728ba6ed26e160a23c1cdb9bc037b +8a30dd2ea24491141047a7dfe1a4af217661c693edf70b534d52ca547625c7397a0d721e568d5b8398595856e80e9730 +ae7e1412feb68c5721922ed9279fb05549b7ef6812a4fd33dbbbd7effab756ab74634f195d0c072143c9f1fd0e1ee483 +b7ce970e06fa9832b82eef572f2902c263fda29fdce9676f575860aae20863046243558ede2c92343616be5184944844 +85ed0531f0e5c1a5d0bfe819d1aa29d6d5ff7f64ad8a0555560f84b72dee78e66931a594c72e1c01b36a877d48e017ca +b8595be631dc5b7ea55b7eb8f2982c74544b1e5befc4984803b1c69727eac0079558182f109e755df3fd64bee00fcaa5 +99e15a66e5b32468ef8813e106271df4f8ba43a57629162832835b8b89402eb32169f3d2c8de1eb40201ce10e346a025 +844c6f5070a8c73fdfb3ed78d1eddca1be31192797ad53d47f98b10b74cc47a325d2bc07f6ee46f05e26cf46a6433efb +974059da7f13da3694ad33f95829eb1e95f3f3bfc35ef5ef0247547d3d8ee919926c3bd473ab8b877ff4faa07fcc8580 +b6f025aecc5698f6243cc531782b760f946efebe0c79b9a09fe99de1da9986d94fa0057003d0f3631c39783e6d84c7d5 +b0c5358bc9c6dfe181c5fdf853b16149536fbb70f82c3b00db8d854aefe4db26f87332c6117f017386af8b40288d08f9 +a3106be5e52b63119040b167ff9874e2670bd059b924b9817c78199317deb5905ae7bff24a8ff170de54a02c34ff40a4 +ad846eb8953a41c37bcd80ad543955942a47953cbc8fb4d766eac5307892d34e17e5549dc14467724205255bc14e9b39 +b16607e7f0f9d3636e659e907af4a086ad4731488f5703f0917c4ce71a696072a14a067db71a3d103530920e1ec50c16 +8ed820e27116e60c412c608582e9bb262eaaf197197c9b7df6d62b21a28b26d49ea6c8bb77dfde821869d9b58025f939 +97bc25201d98cde389dd5c0c223a6f844393b08f75d3b63326343073e467ac23aacef630ddc68545ea874299ba4a3b4f +b73c9695ad2eefd6cc989a251c433fab7d431f5e19f11d415a901762717d1004bb61e0cc4497af5a8abf2d567e59fef4 +adaabe331eea932533a7cc0cf642e2a5e9d60bbc92dd2924d9b429571cbf0d62d32c207b346607a40643c6909b8727e2 +a7b1bbfe2a5e9e8950c7cb4daab44a40c3ffab01dc012ed7fe445f4af47fa56d774a618fafe332ab99cac4dfb5cf4794 +b4a3c454dcd5af850212e8b9ba5fe5c0d958d6b1cabbf6c6cfe3ccbc4d4c943309c18b047256867daf359006a23f3667 +a5c0b32f6cef993834c1381ec57ad1b6f26ae7a8190dd26af0116e73dadc53bb0eeb1911419d609b79ce98b51fdc33bc +ac2f52de3ecf4c437c06c91f35f7ac7d171121d0b16d294a317897918679f3b9db1cef3dd0f43adb6b89fe3030728415 +94722ae6d328b1f8feaf6f0f78804e9b0219de85d6f14e8626c2845681841b2261d3e6a2c5b124086b7931bf89e26b46 +a841a0602385d17afabca3a1bb6039167d75e5ec870fea60cfcaec4863039b4d745f1a008b40ec07bca4e42cb73f0d21 +8c355f0a1886ffced584b4a002607e58ff3f130e9de827e36d38e57cb618c0cb0b2d2dea2966c461cb3a3887ede9aef1 +a6a9817b0fc2fd1786f5ba1a7b3d8595310987fb8d62f50a752c6bb0b2a95b67d03a4adfd13e10aa6190a280b7ee9a67 +a1d2e552581ecbafeaef08e389eaa0b600a139d446e7d0648ac5db8bbbf3c438d59497e3a2874fc692b4924b87ff2f83 +a1b271c55389f25639fe043e831e2c33a8ba045e07683d1468c6edd81fedb91684e4869becfb164330451cfe699c31a8 +8c263426e7f7e52f299d57d047a09b5eeb893644b86f4d149535a5046afd655a36d9e3fdb35f3201c2ccac2323a9582e +b41c242a7f7880c714241a97d56cce658ee6bcb795aec057a7b7c358d65f809eb901e0d51256826727dc0dc1d1887045 +93001b9445813c82f692f94c0dc1e55298f609936b743cf7aae5ebfa86204f38833d3a73f7b67314be67c06a1de5682d +82087536dc5e78422ad631af6c64c8d44f981c195ddea07d5af9bb0e014cdc949c6fa6e42fce823e0087fdb329d50a34 +8e071861ceba2737792741c031f57e0294c4892684506b7c4a0fc8b2f9a0a6b0a5635de3d1e8716c34df0194d789ae86 +b471c997e1e11774bd053f15609d58838a74073a6c089a7a32c37dd3f933badf98c7e5833263f3e77bc0d156a62dd750 +8d2d8686fb065b61714414bb6878fff3f9e1e303c8e02350fd79e2a7f0555ded05557628152c00166ce71c62c4d2feaa +ae4c75274d21c02380730e91de2056c0262ffcecf0cbdb519f0bdb0b5a10ae2d4996b3dc4b3e16dbaea7f0c63d497fef +97140d819e8ca6330e589c6debdee77041c5a9cedb9b8cbd9c541a49207eeb7f6e6b1c7e736ec8ba6b3ab10f7fcd443a +af6659f31f820291a160be452e64d1293aa68b5074b4c066dac169b8d01d0179139504df867dc56e2a6120354fc1f5be +a5e5d8088a368024617bfde6b731bf9eee35fc362bed3f5dfdd399e23a2495f97f17728fec99ca945b3282d1858aa338 +a59cfc79d15dbdde51ab8e5129c97d3baba5a0a09272e6d2f3862370fdbaf90994e522e8bd99d6b14b3bb2e9e5545c6f +a30499b068083b28d6c7ddcc22f6b39b5ec84c8ee31c5630822c50ea736bb9dca41c265cffc6239f1c9ef2fd21476286 +88ffe103eca84bbe7d1e39a1aa599a5c7c9d5533204d5c4e085402a51441bb8efb8971efe936efbbfa05e5cb0d4b8017 +b202356fbf95a4d699154639e8cb03d02112c3e0128aab54d604645d8510a9ba98936028349b661672c3a4b36b9cb45d +8b89bb6574bf3524473cff1ff743abcf1406bd11fb0a72070ccd7d8fce9493b0069fb0c6655252a5164aee9e446ea772 +93247b1038fa7e26667ee6446561d4882dc808d1015daafb705935ddc3598bb1433182c756465960480f7b2de391649e +b027f94d3358cbb8b6c8c227300293a0dee57bf2fee190a456ad82ecfb6c32f8090afa783e2ab16f8139805e1fb69534 +a18bb1849b2f06c1d2214371031d41c76ffa803ee3aa60920d29dbf3db5fbfac2b7383d5d0080ba29ce25c7baa7c306b +827bf9fd647e238d5ac961c661e5bbf694b4c80b3af8079f94a2484cb8fba2c8cf60e472ebcd0b0024d98ae80ad2ff5a +838e891218c626a7f39b8fd546b013587408e8e366ecc636b54f97fa76f0a758bc1effa1d0f9b6b3bc1a7fcc505970a0 +836523b5e8902d6e430c6a12cff01e417d2bd7b402e03904034e3b39755dee540d382778c1abe851d840d318ebedce7f +850a77dda9ac6c217e2ef00bf386a1adec18b7f462f52801c4f541215690502a77ef7519b690e22fdf54dc2109e0ca38 +a8265c6ae7b29fc2bda6a2f99ced0c1945dd514b1c6ca19da84b5269514f48a4f7b2ccbab65c9107cfd5b30b26e5462f +ab3d02ee1f1267e8d9d8f27cc388e218f3af728f1de811242b10e01de83471a1c8f623e282da5a284d77884d9b8cde0e +831edaf4397e22871ea5ddee1e7036bab9cc72f8d955c7d8a97f5e783f40532edbbb444d0520fefcffeab75677864644 +80484487977e4877738744d67b9a35b6c96be579a9faa4a263e692295bb6e01f6e5a059181f3dd0278e2c3c24d10a451 +aae65a18f28c8812617c11ecf30ad525421f31fb389b8b52d7892415e805a133f46d1feca89923f8f5b8234bd233486a +b3a36fd78979e94288b4cefed82f043a7e24a4a8025479cc7eb39591e34603048a41ee606ee03c0b5781ebe26a424399 +b748b3fc0d1e12e876d626a1ba8ad6ad0c1f41ea89c3948e9f7d2666e90173eb9438027fadcd741d3ae0696bd13840f1 +acdd252d7c216c470683a140a808e011c4d5f1b4e91aeb947f099c717b6a3bad6651142cde988330827eb7d19d5fb25c +b9a25556a6ca35db1ed59a1ec6f23343eab207a3146e4fc3324136e411c8dba77efd567938c63a39c2f1c676b07d8cdb +a8db6aef8f5680d2bdb415d7bcaae11de1458678dcb8c90c441d5986c44f83a9e5855662d0c1aace999172d8628d8fe1 +af58147108e9909c3a9710cc186eab598682dca4bfd22481e040b8c000593ecb22c4ede4253ac9504e964dfa95a9b150 +8dd8bb70f1c9aec0fcc9478f24dfc9c3c36c0bf5ff7a67c017fa4dab2ec633fbd7bc9d8aa41ea63e2696971ed7e375f5 +aa98d600b22aff993a4d7a3ccabd314e1825b200cb598f6b797d7e4d6a76d89e34a4d156c06bddfc62f2ef9b4c809d1d +8a8fc960d6c51294b8205d1dabe430bef59bda69824fa5c3c3105bef22ac77c36d2d0f38ffc95ce63731de5544ccbeff +b6d1020efe01dc8032bd1b35e622325d7b9af9dcd5c9c87c48d7d6ebc58644454294c59b7f4b209204b5b1f899f473bf +8a750dc9fe4891f2dfe5759fb985939810e4cdc0b4e243ff324b6143f87676d8cb4bcb9dfb01b550801cedcaaa5349e2 +98c13142d3a9c5f8d452245c40c6dae4327dd958e0fda85255ea0f87e0bcbaa42a3a0bd50407ed2b23f9f6317a8a4bc5 +99f2b83d9ec4fc46085a6d2a70fd0345df10f4a724c1ba4dee082a1fde9e642e3091992ebf5f90a731abcb6ec11f6d9b +b218546ab2db565b2489ea4205b79daa19ef2acbf772ccaaa5e40150e67ea466090d07198444b48e7109939aa2319148 +84f9d1d868e4b55e535f1016558f1789df0daa0ead2d13153e02f715fe8049b1ce79f5bc1b0bbbb0b7e4dd3c04783f3f +80d870d212fbddfdda943e90d35a5a8aa0509a7a1e7f8909f2fcb09c51c3026be47cc7a22620a3063406872105b4f81a +b5b15138ff6551fac535d4bbce2ea6adc516b6b7734b4601c66ec029da2615e3119dc9ad6a937344acfd7b50e4a1a2ae +95d2f97652086e7ceb54e1d32692b1c867ffba23c4325740c7f10d369283d1b389e8afa0df967831ade55696931e7934 +8a5b580403e1a99cd208f707e8ce0d3f658c8280417683f69008d09cc74d835a85f7380f391b36ead9ac66d9eedd1cbe +a8b0c90bff34c86720637b5a2081f0f144cfe2205c1176cacd87d348609bc67af68aed72414dc9aa6f44a82c92c2a890 +865abbdd96c496892c165a8de0f9e73348bf24fce361d7a9048710178a3625881afb0006e9f5ee39124866b87904c904 +ace67bb994adef4b6f841cdf349195608030044562780a7e9b00b58a4ff117268a03ff01e5a3a9d9d7eff1dd01f5f4bf +b9371d59185b3d2d320d3fefeadb06ba2aa7d164352fb8dc37571509509fa214d736d244ac625a09a033a10d51611e2e +a8ef992771422dcf2d6d84386fde9fe5dba88bfded3dfcd14074ca04331b4fd53a7f316615cdfaf10ed932cbb424a153 +868cbc75f8f789ea45eded2768a1dac0763347e0d8e8028d316a21005f17be179d26d5965903e51b037f2f57fe41765d +b607111bcdfd05fa144aa0281b13ee736079ebbbf384d938a60e5e3579639ed8ef8eb9ca184868cdb220a8e130d4a952 +aca55702af5cae4cae65576769effd98858307a71b011841c563b97c2aa5aeb5c4f8645d254f631ed1582df3dbbf17da +b9b5cbace76246e80c20dfcc6f1e2c757a22ab53f7fd9ff8a1d309538b55174e55e557a13bf68f095ff6a4fa637ef21a +8571b0a96871f254e2397c9be495c76379faf347801cb946b94e63212d6a0da61c80e5d7bebbabcd6eaa7f1029172fe5 +902540326281e6dc9c20d9c4deaaf6fbbbcc3d1869bd0cf7f081c0525bea33df5cfa24ead61430fda47fb964fcc7994b +841af09279d3536a666fa072278950fabf27c59fc15f79bd52acb078675f8087f657929c97b4bc761cbade0ecb955541 +a1f958b147ddf80ab2c0746ba11685c4bae37eb25bfa0442e7e1078a00d5311d25499da30f6d168cb9302ea1f2e35091 +863d939381db37d5a5866964be3392a70be460f0353af799d6b3ed6307176972686bd378f8ad457435a4094d27e8dfb7 +835cd4d7f36eff553d17483eb6c041b14280beb82c7c69bca115929658455a1931212976c619bafb8179aed9940a8cc6 +8d0770e3cb8225e39c454a1fc76954118491b59d97193c72c174ecc7613051e5aed48a534016a8cf0795c524f771a010 +91aa4edb82f6f40db2b7bd4789cc08786f6996ebed3cb6f06248e4884bc949793f04a4c5ea6eefe77984b1cc2a45d699 +8fb494ca2449f659ff4838833507a55500a016be9293e76598bbae0a7cb5687e4693757c2b6d76e62bd6c7f19ed080bb +b59b104449a880a282c1dd6a3d8debb1d8814ef35aab5673c1e500ee4cb0e840fb23e05fa5a0af92509c26b97f098f90 +aca908e3bad65e854ae6be6c5db441a06bcd47f5abafdfa8f5a83c8cd3c6e08c33cab139c45887887a478338e19ceb9f +806f5d802040313a31964fc3eb0ee18ac91b348685bed93c13440984ee46f3d2da7194af18c63dea4196549129660a4e +ae4b2dca75c28d8f23b3ab760b19d839f39ff5a3112e33cb44cff22492604a63c382b88ec67be4b0266924dd438c3183 +99d1c29c6bd8bf384e79cd46e30b8f79f9cbc7d3bf980e9d6ffba048f0fc487cac45c364a8a44bb6027ad90721475482 +a16e861c1af76d35528c25bf804bfc41c4e1e91b2927d07d8e96bffe3a781b4934e9d131ecf173be9399800b8269efac +a253303234fb74f5829060cdcef1d98652441ab6db7344b1e470d195a95722675988048d840201c3b98e794b1e8b037c +905ac8a0ea9ce0eb373fb0f83dd4cbe20afb45b9d21ae307846fd4757d4d891b26a6711924e081e2b8151e14a496da18 +b485315791e775b9856cc5a820b10f1fa5028d5b92c2f0e003ba55134e1eddb3eb25f985f2611a2257acf3e7cfdfab5e +b6189c0458b9a043ebc500abc4d88083a3487b7ac47ed5e13ab2a41e0a1bee50d54a406063f92bc96959f19e822a89a7 +a30e15f995fd099a223fc6dc30dad4b8d40bee00caa2bc3223ba6d53cd717c4968a3e90c4618c711ed37cc4cd4c56cf3 +a1b1ed07fcc350bb12a09cd343768d208fc51a6b3486f0ece8f5a52f8a5810b4bc7ab75582ec0bc2770aed52f68eace5 +88aa739fbae4bece147ba51a863e45d5f7203dbc3138975dc5aef1c32656feb35f014d626e0d5b3d8b1a2bda6f547509 +ab570f3c8eabfca325b3a2ea775ef6b0c6e6138c39d53c2310329e8fb162869fde22b0e55688de9eb63d65c37598fca3 +89d274762c02158e27cb37052e296a78f2b643eb7f9ae409f8dac5c587d8b4d82be4ef7c79344a08ebec16ac4a895714 +99c411d2ad531e64f06e604d44c71c7c384424498ecd0a567d31ec380727fb605af76643d0d5513dd0a8d018076dd087 +80d0777fa9f79f4a0f0f937d6de277eec22b3507e2e398f44b16e11e40edf5feff55b3b07a69e95e7e3a1621add5ed58 +b2430a460783f44feb6e4e342106571ef81ad36e3ddd908ec719febeb7acaf4b833de34998f83a1dab8f0137a3744c11 +b8f38ccfc7279e1e30ad7cefc3ea146b0e2dff62430c50a5c72649a4f38f2bac2996124b03af2079d942b47b078cc4f8 +a178a450a62f30ec2832ac13bbc48789549c64fc9d607b766f6d7998558a0e2fad007ae0148fc5747189b713f654e6ba +98c5ede296f3016f6597f7ccc5f82c88fd38ed6dc3d6da3e4a916bfd7c4c95928722a1d02534fe89387c201d70aa6fd2 +a8cc5e98573705d396576e022b2ba2c3e7c7ece45cd8605cb534b511763682582299e91b4bb4100c967019d9f15bbfaf +848480ea7b7d9536e469da721236d932870b7bbee31ccf7ae31b4d98d91413f59b94a1e0d1786ee7342295aa3734969c +b88ea38f9ee432f49e09e4e013b19dff5a50b65453e17caf612155fff6622198f3cba43b2ea493a87e160935aaaf20a9 +949376934a61e0ef8894339c8913b5f3b228fa0ae5c532ad99b8d783b9e4451e4588541f223d87273c0e96c0020d5372 +96f90bb65ca6b476527d32c415814b9e09061648d34993f72f28fae7dc9c197e04ef979f804076d107bb218dfd9cb299 +a4402da95d9942c8f26617e02a7cef0ebc4b757fac72f222a7958e554c82cc216444de93f659e4a1d643b3e55a95d526 +81179cbc26a33f6d339b05ea3e1d6b9e1190bd44e94161ae36357b9cdf1e37d745d45c61735feed64371fe5384102366 +ad4dc22bdbd60e147fdac57d98166de37c727f090059cfc33e5ee6cf85e23c2643996b75cf1b37c63f3dc9d3c57ffa18 +8a9b1b93dc56e078ce3bb61c2b0088fd6c3e303ba6b943231cc79d4a8e8572f4109bbde5f5aa7333aae3287909cb0fe2 +8876ef583bc1513322457a4807d03381ba1f4d13e179260eaa3bddfede8df677b02b176c6c9f74c8e6eab0e5edee6de6 +b6c67e228bf190fbaeb2b7ec34d4717ce710829c3e4964f56ebb7e64dc85058c30be08030fa87cc94f1734c5206aef5f +a00cb53b804ee9e85ce12c0103f12450d977bc54a41195819973c8a06dcb3f46f2bf83c3102db62c92c57ab4dd1e9218 +a7675a64772eefddf8e94636fb7d1d28f277074327c02eea8fae88989de0c5f2dc1efed010f4992d57b5f59a0ab40d69 +8d42bb915e0bf6a62bcdf2d9330eca9b64f9ec36c21ae14bf1d9b0805e5e0228b8a5872be61be8133ad06f11cb77c363 +a5b134de0d76df71af3001f70e65c6d78bed571bc06bfddf40d0baad7ea2767608b1777b7ef4c836a8445949877eeb34 +aeadbc771eaa5de3a353229d33ed8c66e85efbd498e5be467709cb7ff70d3f1a7640002568b0940e3abd7b2da81d2821 +8c28da8e57a388007bd2620106f6226b011ee716a795c5d9f041c810edf9cf7345b2e2e7d06d8a6b6afa1ee01a5badc1 +8ed070626a4d39ffd952ddb177bc68fd35b325312e7c11694c99b691f92a8ea7734aeb96cf9cc73e05b3c1b1dcad6978 +ada83e18e4842f3d8871881d5dbc81aed88a1328298bfdc9e28275094bd88d71b02e7b8501c380fa8d93096cbc62f4fb +8befc3bec82dcf000a94603b4a35c1950ba5d00d4bed12661e4237afa75062aa5dcef8eac0b9803136c76d2dd424a689 +97c6f36c91ca5ca9230bfcbf109d813728b965a29b62e5f54c8e602d14a52ac38fa1270de8bfe1ab365426f3fc3654c7 +b01d192af3d8dbce2fe2fece231449e70eb9ac194ec98e758da11ca53294a0fa8c29b1d23a5d9064b938b259ea3b4fb5 +819a2c20646178f2f02865340db1c3c6ebc18f4e6559dd93aa604388796a34bd9fed28ad3ccc8afc57a5b60bb5c4e4ec +a9ffc877470afc169fecf9ec2dc33253b677371938b0c4ffa10f77bb80089afa2b4488437be90bb1bcf7586a6f4286e3 +b533051c7ce7107176bcb34ad49fdb41fac32d145854d2fe0a561c200dcf242da484156177e2c8f411c3fdf1559ecf83 +8fe2caff2e4241d353110a3618832f1443f7afe171fd14607009a4a0aa18509a4f1367b67913e1235ac19de15e732eb1 +84705c6370619403b9f498059f9869fdf5f188d9d9231a0cb67b1da2e8c906ead51b934286497293698bba269c48aa59 +899dddf312a37e3b10bdaaacc1789d71d710994b6ee2928ac982ad3fd8a4f6167672bc8bf3419412711c591afe801c28 +b2f7916d946b903ded57b9d57025386143410a41a139b183b70aeca09cf43f5089ead1450fce4e6eb4fba2c8f5c5bbe5 +8d5f742fe27a41623b5820914c5ca59f82246010fa974304204839880e5d0db8bc45ebab2ad19287f0de4ac6af25c09e +b93d4a1f6f73ac34da5ffbd2a4199cf1d51888bc930dc3e481b78806f454fcb700b4021af7525b108d49ebbbaa936309 +8606f8d9121512e0217a70249937e5c7f35fbfe019f02248b035fa3a87d607bc23ae66d0443e26a4324f1f8e57fd6a25 +b21312cdec9c2c30dd7e06e9d3151f3c1aceeb0c2f47cf9800cce41521b9d835cb501f98b410dc1d49a310fdda9bc250 +a56420b64286bdddda1e212bba268e9d1ba6bdb7132484bf7f0b9e38099b94a540884079b07c501c519b0813c184f6b4 +80b2cf0e010118cb2260f9c793cef136f8fa7b5e2711703735524e71d43bce2d296c093be41f2f59118cac71f1c5a2ff +adcb12d65163804d2f66b53f313f97152841c3625dbbda765e889b9937195c6fcd55d45cc48ebffabb56a5e5fe041611 +8b8a42e50dc6b08ab2f69fc0f6d45e1ea3f11ba0c1008ee48448d79d1897356599e84f7f9d8a100329ed384d6787cfc4 +aaa9c74afa2dec7eccfbd8bb0fc6f24ed04e74c9e2566c0755a00afdfdf3c4c7c59e2a037ec89c2f20af3fae1dd83b46 +aa9f6e8fd59187171c6083ae433627d702eb78084f59010ff07aff8f821f7022ef5fbbe23d76814d811b720a8bfa6cc3 +a56a3ded501659ad006d679af3287080b7ee8449e579406c2cae9706ef8bf19c1fc2eb2a6f9eaf2d3c7582cded73e477 +81971e077c1da25845840222b4191e65f6d242b264af4e86800f80072d97d2a27a6adc87c3a1cb1b0dd63d233fbafa81 +a6fa5453c4aaad2947969ee856616bf6448224f7c5bf578f440bcfc85a55beb40bef79df8096c4db59d1bd8ef33293ea +87c545adbfaaf71e0ab4bac9ae4e1419718f52b0060e8bb16b33db6d71b7248ae259d8dd4795b36a4bbb17f8fae9fd86 +b4c7a9bc0910e905713291d549cec5309e2d6c9b5ea96954489b1dff2e490a6c8b1fa1e392232575f0a424ba94202f61 +802350b761bcaba21b7afe82c8c6d36ee892b4524ab67e2161a91bbfa1d8e92e7e771efb1f22c14126218dd2cb583957 +b4e7ddb9143d4d78ea8ea54f1c908879877d3c96ee8b5e1cb738949dcfceb3012a464506d8ae97aa99ea1de2abf34e3d +a49a214065c512ad5b7cc45154657a206ef3979aa753b352f8b334411f096d28fd42bca17e57d4baaafb014ac798fc10 +8a80c70a06792678a97fe307520c0bf8ed3669f2617308752a2ab3c76fdf3726b014335a9b4c9cbcfc1df3b9e983c56f +a34721d9e2a0e4d08995a9d986dc9c266c766296d8d85e7b954651ad2ca07e55abb1b215898ee300da9b67114b036e0d +8cfce4564a526d7dca31e013e0531a9510b63845bbbd868d5783875ed45f92c1c369ce4a01d9d541f55f83c2c0a94f03 +ab3f5f03a5afc727778eb3edf70e4249061810eba06dc3b96b718e194c89429c5bfbec4b06f8bce8a2118a2fdce67b59 +aa80c2529fc19d428342c894d4a30cb876169b1a2df81a723ab313a071cba28321de3511a4de7846207e916b395abcc9 +82b7828249bf535ef24547d6618164b3f72691c17ca1268a5ee9052dba0db2fdd9987c8e083307a54399eab11b0f76b1 +8fbcb56b687adad8655a6cf43364a18a434bf635e60512fad2c435cf046f914228fb314f7d8d24d7e5e774fb5ffb1735 +a3010a61a2642f5ebbce7b4bc5d6ecb3df98722a49eb1655fe43c1d4b08f11dfad4bcec3e3f162d4cc7af6a504f4d47c +b3dcc0fdf531478e7c9ef53190aa5607fd053a7d2af6c24a15d74c279dbb47e3c803a1c6517d7e45d6534bb59e3527f5 +8648f6316c898baaca534dff577c38e046b8dfa8f5a14ee7c7bc95d93ae42aa7794ba0f95688a13b554eeb58aeedf9ba +89fca6fc50407695e9315483b24f8b4e75936edf1475bcf609eed1c4370819abac0e6a7c3c44f669560367d805d9ba63 +a367a17db374f34cd50f66fb31ba5b7de9dbe040f23db2dcc1d6811c0e863606f6c51850af203956f3399000f284d05f +91030f9ca0fff3e2dbd5947dcf2eba95eb3dbca92ee2df0ed83a1f73dbf274611af7daf1bb0c5c2ee46893ab87013771 +84d56181f304ce94015ea575afeef1f84ea0c5dbb5d29fb41f25c7f26077b1a495aff74bd713b83bce48c62d7c36e42d +8fe2f84f178739c3e2a2f7dcac5351c52cbed5fa30255c29b9ae603ffd0c1a181da7fb5da40a4a39eec6ce971c328fcf +a6f9b77b2fdf0b9ee98cb6ff61073260b134eb7a428e14154b3aa34f57628e8980c03664c20f65becfe50d2bdd2751d4 +8c6760865445b9327c34d2a1247583694fbeb876055a6a0a9e5cb460e35d0b2c419e7b14768f1cc388a6468c94fd0a0f +af0350672488a96fe0089d633311ac308978a2b891b6dbb40a73882f1bda7381a1a24a03e115ead2937bf9dcd80572ad +a8e528ec2ee78389dd31d8280e07c3fdd84d49556a0969d9d5c134d9a55cd79e1d65463367b9512389f125ed956bc36a +942c66589b24f93e81fe3a3be3db0cd4d15a93fb75260b1f7419f58d66afaa57c8d2d8e6571536790e2b415eec348fd9 +83fe4184b4b277d8bf65fb747b3c944170824b5832751057e43465526560f60da6e5bbee2f183cb20b896a20197168c7 +88a71aada494e22c48db673d9e203eef7a4e551d25063b126017066c7c241ee82bedaa35741de4bd78a3dd8e21a8af44 +8c642a3186ca264aac16ee5e27bd8da7e40e9c67ae159b5d32daa87b7de394bf2d7e80e7efb1a5506c53bfd6edd8c2c3 +81855d6de9a59cef51bef12c72f07f1e0e8fe324fcc7ec3f850a532e96dcd434c247130610aaee413956f56b31cbb0dc +a01e61390dcd56a58ad2fcdb3275704ddfbedef3ba8b7c5fce4814a6cdd03d19d985dba6fd3383d4db089444ea9b9b4d +96494e89cbf3f9b69488a875434302000c2c49b5d07e5ff048a5b4a8147c98291ae222529b61bb66f1903b2e988e5425 +b9689b3e8dddc6ec9d5c42ba9877f02c1779b2c912bba5183778dc2f022b49aed21c61c8ec7e3c02d74fe3f020a15986 +a2a85e213b80b0511395da318cbb9935c87b82c305f717a264155a28a2ea204e9e726bae04ce6f012e331bd6730cbb9d +91b70f44c7d8c5980ce77e9033a34b05781cbe773854d3f49d2905cc711a3d87c20d5d496801ad6fd82438874ce732b8 +884596417ff741bb4d11925d73852ffeea7161c7f232be3bdce9e6bbe7884c3a784f8f1807356ae49d336b7b53a2b495 +ae2aed8ab6951d8d768789f5bc5d638838d290d33ccc152edfb123e88ba04c6272b44294b0c460880451ad7b3868cc6a +89d8ebfb9beebc77189d27de31c55f823da87798a50bca21622cbf871e5d9f1d3182cf32ee9b90f157e6ce298e9efccf +afd00a4db4c2ed93cf047378c9402914b6b3255779f3bb47ded4ab206acb7eaebba0fd7762928e681b1aebcfee994adc +a2e49b6cd32e95d141ebc29f8c0b398bb5e1a04945f09e7e30a4062142111cd7aa712ac0e3e6394cfb73dd854f41ad77 +ae8e714ab6e01812a4de5828d84060f626358bb2b955f6fb99ae887b0d5ce4f67ebc079ab9e27d189bf1d3f24f7c2014 +a3100c1eebf46d604e75ebf78569c25acf938d112b29ccbe1a91582f6bd8ef5548ae3961c808d3fb73936ac244e28dbc +a9a02dcff0e93d47ead9cdddc4759971c2d848580bf50e117eb100cafca6afeaa7b87208513d5f96b1e1440ffc1b0212 +894ab01462137e1b0db7b84920a3b677fbb46c52b6f4c15320ef64f985e0fc05cec84cd48f389ce039779d5376966ea3 +b1e40e8399ee793e5f501c9c43bde23538e3ce473c20a9f914f4a64f5b565748d13ab2406efe40a048965ee4476113e4 +a5a7d97a19e636238968670a916d007bf2ce6ae8e352345d274101d0bbe3ac9b898f5b85814a7e4c433dd22ac2e000ff +b6394c43b82923231d93fd0aa8124b757163ba62df369898b9481f0118cb85375d0caac979a198ece432dbb4eb7cc357 +82d522ae3ff4fe2c607b34b42af6f39c0cf96fcfe1f5b1812fca21c8d20cece78376da86dcbd6cdb140e23c93ae0bcb2 +b6e0d986383bc4955508d35af92f2993e7e89db745f4525948c5274cfd500880cb5a9d58a5b13d96f6368bb266a4433e +b0b4325772ec156571d740c404e1add233fb693579f653b0fae0042b03157d3b904838f05c321d2d30f2dbd27c4d08ad +ac41367250263a2099006ef80c30bac1d2f25731d4874be623b6e315c45b0dc9a65f530fce82fb3dc25bd0610008c760 +b6c0b1ed7df53da04a6f3e796d3bfa186f9551c523bc67898bc0ecfc6b4a4a22f8c4d3bfc740ebf7b9fa5b0ea9431808 +8e78fca17346601219d01e5cd6a4837161a7c8f86fe2a8d93574d8006da5f06ae7c48eea7d2b70992c2a69184619663c +a21f91f47e04fafbfafacf3185b6863766a2d0c324ccac2c3853a4748af5897dbbe31d91473b480f646121339c9bae2d +a464d68786ab1fc64bd8734fce0be6fbe8dc021d3e771ff492ada76eedff466577c25e282b7c8ab4c1fd95ef5ff3631e +829a24badc7714081e03509ccfb00818ce40430682c1c0e4a399cd10b690bda1f921aabcbf1edfb1d8a2e98e6c0cedd6 +87ccf7e4bbcb818ef525435e7a7f039ecbb9c6670b0af163173da38cbdb07f18bc0b40b7e0c771a74e5a4bc8f12dfe2c +94087bd2af9dbeb449eb7f014cfbf3ee4348c0f47cde7dc0ad401a3c18481a8a33b89322227dee0822244965ae5a2abb +896b83ed78724dac8a3d5a75a99de8e056a083690152c303326aa833618b93ef9ec19ab8c6ef0efe9da2dbcccac54431 +821e6a0d7ccf3c7bd6a6cc67cde6c5b92fb96542cb6b4e65a44bbc90bbc40c51ff9e04702cb69dd2452f39a2ff562898 +b35b2096cda729090663a49cb09656c019fef1fc69a88496028d3a258ad2b3fd6d91ab832163eaa0077989f647e85e7e +b7857ef62c56d8bce62476cdb2ab965eddff24d932e20fc992bd820598686defe6cc0a7232d2be342696c2990d80721a +b343d974dfda3f6589043acd25d53aecf7c34b1e980ae135a55cda554ff55e531bc7c2dfe89b0d2c30e523c7b065dad1 +8d139e16a73cd892b75f3f4e445a10d55d1118f8eeafc75b259d098338419e72e950df6ca49cb45677a3c4e16fb19cdc +817b8535bd759da392b2c5760c51b3952ecf663662a137c997f595c533cd561ed7e655673c11144242160e41d1f2dd71 +817ee0f0819b0ccb794df17982d5b4332abff5fec5e23b69579db2767855642156d9b9acccf6ceab43332ccc8d2744dc +9835d2b652aec9b0eba0c8e3b6169567e257a6a3f274ec705dbc250ee63f0f8e4b342e47b9e0c280c778208483d47af8 +b78c40177f54f0e6d03083a4f50d8e56b5aafdb90f1b047bb504777d6e27be5a58170330aee12fbaa5f1e9d4f944acfc +ab8eebacf3806fac7ab951f6a9f3695545e2e3b839ca399a4ef360a73e77f089bb53d3d31dbd84ddfde55e5f013626e0 +96c411fc6aecca39d07d2aff44d94b40814d8cfc4ee5a192fd23b54589b2801694d820a0dd217e44863ccff31dda891b +8249c424a0caf87d4f7ff255950bbc64064d4d1b093324bfe99583e8457c1f50e6996e3517bf281aa9b252c2a7c5a83a +acf6ed86121821a3dd63f3875b185c5ebe024bdb37878c8a8d558943d36db0616545a60db90789c0925295f45d021225 +a37f155621a789f774dd13e57016b8e91b3a2512b5c75377ec8871b22a66db99655d101f57acaecd93115297caabfc21 +92e60ee245bd4d349f1c656e034b1a7f0c6415a39ac4c54d383112734305488b3b90b0145024255735e0a32f38dba656 +acec614e562ccfc93366309cfdc78c7d7ee0a23e3a7782a4fc4807b8803e6ebfb894a489d03e9a3c817ff2ec14813eba +b912f9dd26ed552cb14b007b893e6ed2494d12517e5761dbeb88521270144f8c3eb9571a0ad444b30a8a65e80bd95996 +8375408dae79c547a29e9a9e5d4ec8241b36b82e45e4ca3b0c36d2227c02d17bb171528d3778eac3bbdc75d6c4e8a367 +8c2d0e6e4406836da112edbbb63996408bb3cda4a2712fd245e4bb29a0100fdc89a2746d859b84a94565bc1cfa681813 +a7431bf59e111c072d28c97626cd54fcdf018421d053a787d2aef454b91251ee8ff9d3702d06b088f92b9ad2bbebff15 +8f3659b0fbeb90b7f30b7a49233325e806551a32911a654dca86e290b314483bbb33fe6482387bc48c35d85c1dd0441c +8dca5ba23f0bb76f7dacabf12886053552ba829a72827b472a2f01e19a893155cdce65f1fb670000f43e8c75ba015a31 +8c1514c083c77624eeb5d995d60994a2866192e15c4474d0be4189fae0e9dbd62494ebb4c02fbc176b53be548abbc5a1 +80498d2ed153381baf3b0f81da839ed0eea6af5796c422b8e59be805dba48c4395bb97824ac308170bb4f14f319c5ddf +84f5ebc3bf96362457993e9fa31493c31c4283075e2403f63d581b6b0db8a3df294b2085643f2007f4de38cb5d627776 +958e6e38774da518193a98397978dbc73d1c3827b4996ec00b4183da2c305a187a0ada9aa306242814b229a395be83c9 +ab8b8fbf73845615e7fab3e09e96cc181159eab09f36b4c1239b3c03313c9aeb4bbb51e16316fe338b2319ed2571b810 +977e4e33b33bd53394e591eba4f9a183e13704c61e467d74b28f4ad0b69aa51501a5221cb1e0e42bcb548ca518caa619 +a9bb7ecb9846cc30d04aad56d253c3df7004cebb272f6adf7b40a84adef9f57291e0d08d64c961b9fc406cdb198aab9b +8d2b72dc36406a545a9da44e1fddfb953d4894710ca026d6421a4ac91e02d0373a599f2acfe41d8258bc9679cf6f43d3 +904192fc8fe250f61ecb8a36abbbccae85f592bbf00c10039c30b5a1c733d752a04e4fd8a1000c6578616f8a16aa83a3 +87f5fdfe20bbbf931b529ec9be77bbfcc398cad9d932d29f62c846e08a91d2f47ae56ad5345122d62a56f629f9a76c4d +84cc3a53b2e7b7e03015f796b6cb7c32d6ded95c5b49c233ac27fafa792994b43c93cda6e618b66fce381f3db69838ba +aab58da10d7bbe091788988d43d66a335644f3d0897bbc98df27dcc0c0fcee0ac72e24f1abdd77e25196a1d0d0728e98 +a10ea8677c2b7da563d84aa91a314a54cab27bb417c257826ebdd3b045d2a0f12729fe630bbbf785d04874f99f26bee8 +acc4970ef2a4435937a9b8a5a5a311226ca188d8f26af1adfcd6efb2376a59155b9a9ff1cff591bde4b684887d5da6e5 +8dc7cf6fcca483c44eb55e7fb924bf3f76cf79b411ae4b01c6c968910877ac9c166b71350f4d935f19bdffb056477961 +ac2dd1182ded2054c2f4dbf27b71a0b517fb57193733a4e4e56aca8a069cff5078ffd3fd033683d076c1c639a4de63c7 +932ec87c450cd0dc678daf8c63cd1bf46124fa472934e517fbbfb78199f288ff7f354b36e0cc6c8739d3f496cfe0913b +b0d631ced213e8492be60ea334dbe3b7799b86d85d5e8e70d02beef3ae87b1d76e1df3bdb5f7ba8a41904c96f6a64455 +929d7239ead7575867e26b536b8badf2e11ca37840034d0e5c77039f8cce122eff5a1bf6e0bcadde6b3858e9f483d475 +aaae5d372d02ee25b14de585af6fbc48f2c7cd2a6af4f08352951b45aa469599eff41e820df642ca1a0f881120e89dbe +b23c411741a6b059f04fa4f5fd9dd10e2a64915f2de6ea31e39c32f2f347a776a953320e5f7613fcb1167efe502f5c5c +a4581b0ae633fe29c6f09928e5efb16db019eeac57f79fef2fa1d3c9bee42ce0e852bc60b9d0133265373747e52a67a4 +81b33afffd7b2575d4a9a1c5dd6eee675c084f82e06b9b3a52a3c9f76e087f12dca6e0ffddc42fb81ce1adb559d47a38 +89cc890f06b424591556aabdfdbb36d7a23700425e90c9cfed7d3da226b4debe414ac5bdf175273828ce6c5355712514 +a4399438be75cfae2bf825496704da5ed9001bed8538d8ac346c8cf0d4407808e9ee67573eb95fe1c6872ac21f639aaa +ad537f7ce74a1ca9a46fc06f15c1c8a6c32363bd6ac78a3c579ed8f84252e38a914cac16709fe65360e822ef47896de4 +8e53b69f5e3e86b86299452e20ea8068b49565d0d0ab5d50ce00158a18403ae44e1b078a3cfd3f919aa81eb049a30c6e +a59f2542c67a430fd3526215c60c02353ee18af2ff87cb6231a2564fe59b8efec421f18d8b8cc7f084675ecf57b3fd05 +b8d9bac93ef56cb4026dd1c731d92260a608fd55b8321e39166678e1dab834d0efddb717685da87786caeb1aaf258089 +aa2df56f4c6fe9e0f899116c37302675f796a1608338700f05a13e779eb7cf278e01947864a8c2c74cc9d9a763804446 +b0108ff2e327dcb6982961232bf7a9a0356d4297902f4b38d380ff1b954bfbcae0093df0f133dd9e84d5966c7b1aada7 +b06b813b01fe7f8cf05b79dc95006f0c01d73101583d456278d71cd78638df2b1115897072b20947943fa263ddab0cd6 +aa41e6c4d50da8abf0ea3c3901412fe9c9dff885383e2c0c0c50ed2f770ada888a27ea08bbb5342b5ff402e7b1230f12 +a48635dbb7debac10cb93d422c2910e5358ba0c584b73f9845028af4a763fd20da8f928b54b27782b27ca47e631ebf38 +80a574c208e994799e4fa9ef895163f33153bc6487491d817c4049e376054c641c4717bda8efbeb09152fa421a7268a7 +b592bfd78ae228afc219c186589b9b0b5c571e314976d1ed5c1642db9159d577679a73c049cfc3dcfefcd5a4f174eeea +aa1f08af3918c61eadf567a5b1a3cdcdfb1b925f23f1f9e3c47889762f4d979d64686ce1ce990055ef8c1030d98daa3b +857df4cfd56d41c6d0c7fcc1c657e83c888253bae58d33b86e0803a37461be5a57140a77fb4b61108d1d8565091ada1c +8fae66a72361df509d253012a94160d84d0b2260822c788927d32fd3c89500500908c8f850ef70df68ddaeb077fd0820 +aa1dbefc9aef1e7b896ff7303837053c63cfb5c8a3d8204680d3228ac16c23636748fe59286468c99699ae668e769a0c +b64b1cb2ba28665ed10bad1dddc42f3f97383c39bad463c6615b527302e2aaf93eb6062946d2150bd41c329697d101be +b6d35e3b524186e9065cee73ea17c082feff1811b5ab5519dd7991cdff2f397e3a79655969755309bd08c7d5a66f5d78 +a4dae7f584270743bbba8bb633bdb8bc4dcc43580e53d3e9e509ff6c327e384f14104b5bdfe5c662dc6568806950da37 +aae84d3d9ad4e237b07c199813a42ed2af3bf641339c342d9abf7ebec29b5bd06249c4488ce5c9277d87f7b71b3ddd37 +b82a463cf643821618a058bddf9f2acb34ac86a8de42a0fa18c9626e51c20351d27a9575398a31227e21e291b0da183e +8b6c921e8707aded3ea693f490322971b1a7f64786ef071bc9826c73a06bd8ae6bf21bc980425769627b529d30b253ce +80724937b27fc50f033c11c50835c632369f0905f413b1713a2b0a2274bec5d7a30438e94193d479ba6679dbe09a65ef +a1d9b259a2ca9cff8af6678b3af0a290c2f51e9cf26d5fe3c6a4fa3d28cbf33cb709b7f78b4f61cb9419427983c61925 +96a3e69a5ed7a98ce59c4481f2ffb75be9542122ad0eb4952c84d4536760df217854d4ec561ce2f4a79d3793c22fa4f4 +990c4d9a4a22d63a8976d34833cafc35936b165f04aed3504e9b435f0de1be4c83b097bbaa062483cf3dee3833b4f5b6 +b9bf5e4b270aec4a0dc219457b5fed984b548892c4b700482525ba1a7df19284464f841dab94abfabcaa9a7b7a757484 +acaecf49cb4786d17cf867d7a93bd4ffee0781766e11b5c1b29089ae0024c859d11b45828fbff5330b888543264d74a9 +b0e1a0865b1e6f9e4a0e31d0c885526ac06678acc526fda5124742a2c303bd0e8871a0cb7951ec8ed9540fc247c8d844 +82b3d327b3d1a631758451e12870816956cd0cef91fcf313a90dd533d5291193a0ff3cc447054564ce68c9b027a7ffd7 +a2843602abb98f0f83e000f3415039788da1e9a096bfe8fed6b99bab96df948c814560424ffebe755cb72f40436fb590 +ab1c7b43cf838798d1e314bc26e04fc021e99a7bfbfc8ffde62fa8d7f92139de86a377289d5177021154229de01ede15 +95e5cf5dd87ae3aed41b03c6c55f9dfad38dc126b17e7e587c156f7745c8da0bd1d60acb718fc1a03b61344f01e3de4d +86f021a3762bb47167f80d4ef1b1c873a91fe83409f9704f192efeebbc3ece0729cd2f92f63419907ea38ae47bc907d2 +aaa1445dafbbcd645d4332d9806225e9346ee5ac6b22ad45e8922134fe12f3d433f567a6a4c19efdd9d5775a7de1e92f +8fd7e15688eef75df7b8bca3d61bc9fca4f56e047cdb6d0b864e7d1c4966eac27d6094b0c8482b49739f83ec51050198 +80aab8b4d394eb011d4ec6a4c2815617308c9b847c6fa6a3d7e6af1c79420ef6ff2a13934a398581c40ee4cf1cac02ac +8970b97ac076a1d8a321ce00eada0edf974a46bf3cc26f6854e4218cdfc8d2b0c32199d9658f254b4fbae5a2c5535f41 +a1aa2ec5b03df0a630e73dd048680ed6d3032c324941423f45cd1f16038789e5e75b876a13948732e9079a422f66a9fc +b5fe5f5e2f2ae2beeb8e95859a02fc45f01f9fb0ebb2bd8ec9ec976b3e806228821a9775096d341d662bc536c4d89452 +a2bc1f170b62d0d5788b02391337b2ab157c38e725694e80aeead7383e05599be0e2f0fa27ef05db007061809356e147 +a8a69701d4a8d0d972390e9f831fd8e9f424b2c2ef069e56bd763e9e835b3ce5f7cf5de5e5c297c06ace4aa74df1067c +b43d551af4ff3873557efe3f3fb98e5ede9008492f181f4796dd1a6bcda8b9445c155e8146966baa812afae1abe06b48 +b4b1dae44fd596813f30602ab20e9b1fb20cb1bd650daacc97b7e054e5c0178b8131d439a9e5b142ca483cc012a362b3 +b95b8a94c30a831eaaebea98c65cc5d0228c78afd6603d4aa426d8186aecc951f1a11c33951f51df04c7e6fa43ffb5ae +b100059624cf9db371bec80013a57a8f296d006c139a8766308f1ea821c7eccc26cad65bc640ab3f6cef9062653bf17d +8e5a2cb76716e0000d13bce5ef87acac307362a6096f090f5f64e5c5c71a10fddfdee8435e7166ba8c3ad8c3f540f3e4 +93d2c43e21588c1e83c4255c52604b4ac3f40e656352d1827e95dd5222a45aebff9674e34fbbe7ed21eca77bd9b8dcbc +8aeaed611546bb9073b07512a9a1f38a7f436ab45e11775a0f9754baaf63e9bcc7bb59b47546a5ded5e4ba2f698e3b5f +af9e6792e74a1163fe27612f999a2f3cfa9048914c5bef69e3b2a75162bb0ce6ece81af699ad7f0c5278a8df0ba000d2 +850bf2d5d34791c371a36404036ad6fdcd8fb62d1bb17a57e88bda7a78ea322397ce24d1abf4d0c89b9cf0b4cc42feb3 +87f7e2a1625e2b7861b11d593aaac933ed08a7c768aebd00a45d893ed295bbb6ed865037b152bb574d70be006ddc1791 +8dcce8f4ad163b29a2348ea15431c2c6ea1189ece88d2790e9f46b9125bd790b22503ec391bc2dee8f35419863b2c50c +b4bf5266c37f12421dd684b29517982d5e4b65dfdfba5fc7bd7479fd854aabf250627498f1e1188a51c0a88d848ec951 +8651623c690247f747af8fdffdc3e5f73d0662bc3279fa2423a3c654af9b6433b9e5e0155f1ce53857e67388e7e3401d +b155120f196d52760129dde2e2b1990039b99484cdc948fa98095cd23da87679850f522e5955eae34ac267d2144160d3 +aec8115e8d7b6601fbceeccf92e35845a06706d46acd188452c9f7d49abef14c6b3a9a9369a8bab2fd4eb9288e2aaca5 +998a8ca4dc0f145f67a8c456f1d6a7323c4836fe036dcbb0f27eb1c596d121eb97369638a9908cfaf218c7706f266245 +b235fbafac62802742ee3d26b1f4e887f7d2da4d711ba7f9bb6ca024de7beec1de66bb830ce96d69538f7dcb93c51b26 +9258d2ddc21ab4e3edcde7eb7f6a382a29f1b626003cc6fdd8858be90f4ad13240072d8a8d44ef8de51ca4f477fa6c45 +99d038487821c948142c678acd8c792960993dd8cb5e02cb229153a1ee9f88249f4ad9007f08e5d82e2a71fb96bb5f32 +a88ee9dbc73d3d8e0f447b76fdb3a27936bde479a58d5799176885583dc93830ac58bca9087075950ea75100cf51af23 +88b9b15816e5a0387153c1f4b90f613beb3ea4596037da01a81fdd2bcbd0baf5598db99f77e7694e5a0d35e822758108 +907ae4b637d06b15846ee27d08c9c9af42df261c5bdd10cf5bc71f8e5ca34b33ac2405307023c50bdb8dc7b98a2cd5fe +9393d6900e1d2d1a1e42412fefd99578d9ac1d855c90a3e7930a739085496448609d674ca9b34016ad91f22d1cac538e +a28ac56b216730b7dcdb5ab3fc22d424c21a677db99a9897a89ed253ea83acfd9d83125133f5be6d9cd92298df110af8 +b027590ee8766f1e352f831fda732adbaf77152485223ad5489ef3b0ce2d2e9f98d547c111fe133847ebb738987fb928 +a9cc08fbd5c3fee8f77cf6eb996a5cafa195df5134dab000e4d0312f970a5577942ee89794e618074f49841f1f933a42 +a8b3535c3df0b1a409d3fc740527ee7dd5ac21756115cde6f87f98cc7623f50cfcf16790689cab113ee7c35a5bd4879f +b61420227b97e5603ae8a716c6759b619f02b8fdc48acbf854352aa6519dad74b97bacc1723ca564cbf3ca48539ed773 +853762498de80eebf955a6c8ddd259af463e4e25f0b6ba7b6a27b19bdbf4c585de55760a16e2d9345cdba6b2a02610f3 +a711c1b13fc6c30745203c5d06390e6c82bd7c50f61734aa8d99c626faba30119bc910be63ec916c91ba53f8483c05a8 +b488c0a793f4481f46b5875d96eecd73e46209a91677769f0890c5e002ecd7d4b1c9f4ba68c47fbed40e3857b1d8717a +a651c5e812ae65b1c66d92c607e80be330737ea49c1dcfe019c0ecea0f41a320406935bb09206a4abff0d1c24599b9ad +85e34e7d96e4b97db98a43247b6c244383b11ca10bf4777364acf509a6faa618bc973e2136a4693fbc8ab597e308fd5a +99837214102b394fffa7f3883759554c6bb7a070f5c809303595a44195e02b9a169460dc6bbffb62bdc0e7ced5f0a5c1 +a952f89c0afb4bdae8c62b89cc3cfb60d0576ba4fe01a5d99534792f38d8848d919b3fc7577435d8443a044d2ee0bcfa +a1ac1f81acb29798acdfc493854663519e2d1b0e9d23d286ce33882c34b4c1c0bb43dd9638166d8026315a44d9ec92a8 +ac9c58aa38219ae659d23007cc7b97fd25b7b610b2d81a8f9f94ddb089efc49c049a8ea4c56e6eaf7b6498f422a97b3c +87e61d501c242b484fb9a937ef21d485f6678d75257fc8fe831b528979068cadbe7e12b49c34058ec96d70a9d179ab14 +aa45f6852f35cc8b65a4a8b5380641d2602a4fa4e3a035db9664df3ac2e170b1280c4a8b7b55161430063e54de4158a6 +a46975614ddde6d134753c8d82c381966f87203d6e5a5fb99a93b0d43aa461466b37f07b8d0973a1abd6ee2b40f24348 +8d35f97297773422351f4d99564c1359ef1a10cfb60aa0e6c8985a78f39b4268486312c8ebf9dd2ef50a771aa03158eb +8497c6242102d21e8b3ade9a9896c96308ab39171ab74cbd94e304c47598e2c2a7b0a0822492ac5c076ba91d4176481d +973f8fcb5f26915b3a3ef6fe58cc44bc7f4e115cd0ad9727d8d1b8113e126ae2e253a19922c5433be4ab2311a839c214 +ae3ee9f1d765a9baf54b4617a289c3b24930aa8d57658a6b0b113bbf9b000c4a78499296c6f428bbb64755dfd4f795d2 +a5be7a8e522ef3dcf9d2951220faf22bb865d050f4af2880b8483222ff7aad7c0866219fcc573df9d829c6efbb517f98 +a5f3c7fabd7853a57695c5ad6d5b99167d08b5414e35ed1068ae386e0cb1ee2afbbe4d2b9024379b6fc3b10c39024d36 +978d5592d4798c9e6baceff095413589461267d6a5b56cd558ec85011342da16f4365d879b905168256f61d36d891b1f +b7b6eaffa095ecbd76d6e1e88ceebabaf674d9ef7e331e875c6d9b9faa1762c800ff1ea597c214c28080f67a50a96c1e +8a1ab53ae5ceaa42e06e58dd8faf6c215fc09ba111ca9eeb800612334d30d5971448be90fec62ed194328aadd8c8eecc +a9ca532cac8ace9a9e845382f8a7840bf40cb426f2fcad8a2f40aadbb400b3a74021627cc9351b0966b841b30284962e +8dddeda8854c8e7ddc52676dd1d0fed1da610ed5415ddd7d25b835bd8420a6f83d7b67ec682270c9648b2e2186343591 +888906aac64fd41d5c518a832d4e044fdc430cfe142fd431caf4676cafc58853ce576f098910d729011be0a9d50d67b5 +96a3f886a2824e750b1e2ea5c587132f52a0c5e3ff192260d8783c666206bd8ebd539933816d7cdd97e4bc374e0b1edf +a150a29ffb2632cc7ec560983d9804cd6da3596c0c25956d27eb04776508eae809659fc883834269437871735de5f9ed +81f7ad4d2959d9d4009d1dfbc6fee38f930f163eb5eac11e98dc38bd2f7f224e3f5c767583f8e52d58d34f3417a6cf90 +97ccac905ea7d9c6349132dd0397b6a2de9e57fd2d70f55e50860e019de15c20171a50b28a5c00ef90d43b838253b3d1 +95694f00c21e8a205d6cbda09956b5b6ec9242ec8c799a91f515b07dcc7de3b6f573e2c0ba149f5a83700cda2d1df0f5 +82bbc3c4a3b3997584903db30fffd182a266c7d1df3e913f908d5a53122fa12cf5acd11d915d85d5bd110fcc43cee736 +8d3f24b4949aa1b4162c28dfbb9f813dd1d8b330f71325448dc45ea34d59b69ca95059402aae011e1b5aba6e536bc6ec +92c734c19752d24782331e74c9af97a8399ddfdd32954e91cda7363dba876aca4f730b451c50a8913950420682da8121 +8653d2c79f77b8c7dcdf7e8dee42433998aeedf1b583abfca686d47a854de1b75e9a4351580c96d1a2a9532659203361 +886f0e414cb558c1a534a1916d3531320a9b6024639712ffe18164ce6313993a553e2b9aafe9c0716318f81a5d0bb1da +b31b5efaba5a5020c3bcea0f54860e0688c2c3f27b9b0e44b45d745158f484e474d5d3b1a0044dd6753c7fb4bf8ace34 +b2d615bbdfdc042d6f67a6170127392d99f0e77ae17b0e1be6786ff2f281795f1bf11f83f2e0f8723b5cdd1db1856e09 +a6e014cca531e6ac2922239b5bee39d69d9ba6d0fa96a4b812217dd342657d35606f0b9c5a317efd423cdb1047815e3d +a8921736b69c9fbb29f443715174bac753e908251804620c542fad6cfbfda7bdfe287f2902f30b043a8a4b4818cfdeef +8d73a9949a042ec2dcefa476e454cd9877eee543b1a6b3b96a78ffcff87421e8b26dd54d5b3192ac32073cb36497acc3 +b936a71ee8df0e48867f3790adf55dc8efc6585024128de2495f8873bd00fd9fa0984472125e801ed9c3cdce6698f160 +82f69c06209c28f64874e850601dda56af44ffc864f42efa8f9c6a0758207bf0a00f583840982dec0a517ab899a98e5b +b7a0a14411101473406f30e82f14b13e6efc9699e7193c0be04bb43d1b49e8c54812ce0f9b39131a20379c4c39d3bbe3 +81159c969f38107af3b858d7582b22925a7ccced02fae3698482d7e9cdc6c568e959651991c6cf16c53a997442054b61 +8bf1116a206e0ce9199fcab6ed2b44a9e46e8143bff3ed3f1431f8d55508fe2728b8902670cfd8d9b316f575f288ed9d +a279b2149824b64144eb92f5a36b22036d34a52bd5a66e5da4b61fbc95af6eda8e485c7914f448abd8674fc14d268d9d +8b98279b5f3588d1a2f8589d2756458690a502728800f8d94b28e00df842a101c96ab9c5aee87c5bbe65552c0c383b80 +b4a27a351ec54420f94e0a0a79d7c7a7337940399646631baca93eeab5fd429d7fb39428be77dcbce64a13eaa3c8ca1d +90c08baa29ec8338ffce381eae3d23ce3f6ba54e5242dec21dc3caaed69cac13f2ab5e8d9d719bc95720fa182eee399c +85156d65bb4fef69ffd539ab918b3286105ca6f1c36a74351ab3310b339727483433e8f8784791f47b4ba35ca933c379 +923005013c27209d07c06a6b92b0cbb248a69c5e15c600bbcc643e8dcd2402adebd94dd4cafb44ec422a127e9780aaec +863b23eb5463a6ef5a12039edc2f8e18e3c97b244841bc50af02459b1bcc558367edf2f6e4fe69f45f37887469dd536d +87a4a7708a112724ff9b69ebb25d623b5cae362ae0946daed2ec80e917800dbfcd69f999c253542533242e7b9a5cc959 +8bf4347ceea7f94b53564f26b1a4749a16f13bf71a9e03a546f906f7c423089820ff217066159b0637d9d6824e9c101c +ab07eef925d264145971628a39e4dd93ff849767f68ed06065802cf22756fc6bf384cf6d9ab174bfc1a87bcc37b037aa +8e3f10a42fad43887d522dc76b1480063267991c2457c39f1e790e0c16c03e38a4c8e79a0b7622892464957bf517ebd8 +a8722fc7b1acf0be18f6ddf3ee97a5a9b02a98da5bc1126a8b7bf10d18ee415be9a85668eb604ef5a1f48659bc447eb5 +878d6b2a9c0aca8e2bc2a5eb7dd8d842aa839bbd7754860c396a641d5794eab88a55f8448de7dbddf9e201cbc54fe481 +ada881c167d39d368c1e9b283cf50491c6bfc66072815608ba23ab468cfbd31ca1bd7f140e158e0d9e4d7ebfa670bc2d +a2b48578fa899d77a7ee1b9cb1e228b40c20b303b3d403fd6612649c81e7db5a7313ba9702adc89627b5fd7439f8b754 +8e051280e10551558dcb5522120ac9216281c29071c0371aaa9bde52961fe26b21d78de3f98cb8cd63e65cff86d1b25c +a7c5022047930c958e499e8051056c5244ae03beb60d4ba9fe666ab77a913a067324dfb6debcb4da4694645145716c9d +95cff6ec03e38c5ab0f6f8dccde252d91856093d8429b7494efc7772996e7985d2d6965307c7fdfa484559c129cca9f9 +993eb550d5e8661791f63e2fa259ab1f78a0e3edad467eb419b076a70923fede2e00ddc48a961d20001aaae89fad11e8 +abb2826e4d4b381d64787a09934b9c4fe1d5f5742f90858228e484f3c546e16ee8a2a0b0a952d834a93154a8b18f3d16 +a922ca9f2061996e65ef38a7c5c7755e59d8d5ce27d577abcdd8165b23b4877398d735f9cb470a771335fc7d99ecb7fc +90f22862216f6bc1bbf5437740a47605d1ff5147b1f06f7b13fec446e4c5a4a4a84792cb244a1905f3478a36f8d7065b +87f3d9a86afef5b79ea1ca690ee1ee4bb9754b66f7c50a42ad6b99af7c222c853ca161f440a0a2a60b3b5a54e3493240 +80a9ca9a2d33b9cf61976b3860d79f5d00de89a06ef043d2a52931809018aeb4ce70423cbef375b29c2c750c2c8704c2 +b4e798ef1d615896108dae37ac50c1e859216ab6dbac11653e44d06ce5209057b4b0dd6d31dcfcda87664a23c8ef1cbd +aaed6d1e7c5b1db06f80dae6c24857daadfb0268f20e48a98fba4b76de1ebf65fb84c3be95fd6a418b498f8285ec63bd +aeceaa316c6369492c939f94809bc80e0857abac86c0d85be8066bbf61afbaaec67e28c572437a8d35c49dd596b3134f +b791c3d53ed34a7d1c8aa89b7953e3684c3cd529230824dc529739a5fbe74b58b87f01e56e7a169f61c508237ef67160 +9351f8c80634386c45c0050d2f813193f9d839173be941e2092d729be5403632a2f18dffdc323d69eb0dc31fa31c5866 +97693184d5c0056ae244dfb6709cafa23a795dc22d497a307a7f9cf442d7452024023c54a8d6bda5d90a355ba2c84f3a +85362daa003d23511ca174a8caafe83d52b6436dc4e43c4c049e5388d9211b5cbef3885896914d86d39be0dd1f910511 +a2511b5fa34b24eeb0e1bcbcf872a569d1ff5570fe7b0fb48f5542f7fe57bad808d34b50afa87580866a6cb0eba02f27 +b382e3327eb1401f2d378dbb56ac7250adde0961bd718575a64d264ffd44772c20752d4035c3ba60eb435e160b375e20 +afad8a5d40b536c0720556845a6b257ed42165c14fb4b4a874717d107752f49ed9380c5b048df3aca67287bb8fc411a8 +8fad0c98434ca5373c2d767868f679b76b4a8d04bca8240ea3f388558262c2d61b73b16fc1160932652b5688c25fffcf +83898008b5cbb6f08f8ef3ec179427869682bb4e8d38f6e6a687a214d4a307436afc64ee67d70a5a8ba9730bf839aecc +b85232e79913785fd82b06890706972b4ad7a309489930ae23390d51aa5189731f8a2df24800409a8c36b3dd6fc91275 +a24ff26ec792f3701da4c5638c1fca4fa4dae95b01827d6200d583c4caf17ea3171393ba2a8c23d1ee8b88402916f176 +adc5c7a7ff6b41d6cc386b7fc69d7bb04179bdf267864f9aa577f0f6a88438191fa81ebaf13055c2f2d7290be6421ace +a05e835abd502d31454d40a019010ff90b6b0b1f993075a35c9907aeab7a342ac0ba6144dc9379aada6119157970e9b2 +85ff07ba58463e7f153fc83f11302e9061e648a5cbd272bb0545030b20e11facd8b3ff90c9ac8c280a704fbda5c9d1b0 +a6c735ada8f4587da8cdad7ea3ada01650b5a3ecab8d81daa7a5f5de51ef4a6592b524692584306f06be3f6701f2870c +b138deee4e53ae8d677fae104f713ef1b8babfecec16b6a85785a66a72784eb09d44c3b63567222ade714e98f7d1604e +ae79c1a49dafcdd972acd95d8ad0a35c02adc7fd736d4c44c3cd13df5789d339b5ea16bddbbd43e486a061ab31baa5c0 +ab3cf2371a1d7dcd0ffe3869a0178230964b06694bf258b2073ea66a2afccd845b38485da83d02e1d607d4c5c36b78a8 +ab9609f28a325fd01cb39540e3a714506c44e52ef28ee640f361deb5760aadbb23e804663b0fa20a66e239c33f8d8bb8 +8ed95ea8e76e1b42823d7915a6aae77d93746f846bf602841dfce0e47543a36efb9ee7e5b42c73c3209d911225cc471b +a80b6162036d43811482323f0ce59eb18740e33a63d7c7bbbf3be206985919e5342d53a69df537d43e8b7d7f51e8892f +93c03d0a5083408ba00c125a8a9385213d4c860072f0297857b1235045819b904e07f2425c13a661d0a01d2e53347f4b +a6581200f00f96c461621e1d26b14a23687dd97eb9f7df4ba641a84340ee7306dc1796248fba4804f185947ad13b4385 +8be174018fa40f7e0cedc5ae68f38969eb7695f2205e9c573641e533d56f68c20abf38a23d2f0dcac371e60b21b18615 +857ad4ee3218c647c58f09b8ab22bcc8976f00a768ab1f708618e868e6143474be846422ce2710a0ed39b5155b6f13a1 +a490bec40f322d599f26bcefcdddd8f2ef6576aa737d5ce7e8d5d422741abe749e3e6a48489aed8c560633f72857e3c2 +a9c0ee339621f1c4a2410f9b4d2f03f1b558dae2973807b8bccd920e8feb7f65dfde3e79986b72ad21fcc4567240381d +8592251568e750a430f7d2c6ddbb3ec82a4dd9fd83efe389e69aa177fd97ac2c96c59a6e86db20d8e6f125d65b46c4d3 +a4e2f4aa6a682913b423b097c4069c4e46a1f3af9556b1bfd0580d0fc01e3991488458049e0735b2a629684a79271c8f +8c4f6a3e738cf74112b08b1680be08158013ef8a515a81215d8a36c9b756786d1b4cb4563923463f3329292f4b48bf6d +8bace547353c02ea00dd547eeda7259aa354d4772dd5e0c486c723cf88627b7112e196b879c3c92a9561b674d9fc486d +8d372f4901e25e8db64fa098148d4a4e709b0e9dcb756d0f90dad99dea393054193ae1a33d292a3dd772ff7ba05e4b71 +a8c7ea6a6a031ed23d65639f01f5423190775558f479700597df7ae7e338a6ae5e9b32f470aff20787ac8b7eec84df6c +b6e9dcba240fdbbf66033410a79a2dd3e9e1ffdf2eae949b3a9ed720e939d92339991dc3e70a5ac7d5253f317daf0b7d +974dec4cd61af75721071752c664d9c2a5121f06ff1515c56139a177a3ca825f763b69d431d4607e393fa74dcc91cc58 +958863e6ad583a9d370a6db3639066982e44766904e7afa849b132f6666b7d08ab931131b3bec7a506d6583e93d56767 +8b93a33b5da9b3300c20a96d80b894e3789c77041183c2cb21751579c8c96857f60cfc2f075201b64e95a78985c5b321 +b726cb9f7ef34ddbc2fad82b3b0af0b30cc913e26c5a614ae5c19cc9c55c8e6dae069db5315a8dcb6d987415bb550ca8 +a730f515398a71bddd66cab2ff996659d4e47dfbb08ce7958a41021f76d269b91c7498b708cd14b183a8ef469c772803 +a4eb3b18132eb0f5337f14e01d63ca0bec0db6a43870f800e5491db756c2f5fce519d8dba5528b4bcef550d06b33699c +b1ab6621eec1ee6784e632e214693f39a14f3715991996b883d66200963e065c86fa0667f7bc36b93b40b5d90ff708c2 +80486a26c3532ad6e19f76d8c9344e2626c07363fd495264927cb5935fa9565ece670dc98767afb04af6a9a5c9231075 +8ee20e0df3c84a1c6b0e21bcc325cf99235b747ffe47f17fdfba548a358ca75cbcc331dd50db2311b400ae882256a608 +aef4268959e5541e7ec69c921a1e81a8374d7e44bf1bb2debf4101cf3cd6b7d6ca7f441758b388de96b3e0edb5b97be9 +8793629bd29d689ec94b016de8886cac6e2ca6638911babb22db4a787661422da0639a4e4089ebeb689d173abfe75950 +b487b3551c20a29e9a5abbda8c50ff594826283e443c09e3ae09b914e46060b3f9abf70434444ce1487e2a74e562616b +8f11531cfc5997dd04b997cb87ba1831aa7041d5434fe72de66304e3f165d882fac891391fbb1eb955c65319e65293b6 +b195136875fd02a75676c33cb3e60504d5964f7a9e81f4c8c8fd38af62e2145c55f765b3158664566191188ac678f381 +b374174b0b3eb04fa49eb4ece45173f0db5d829eac370a20a62309566e0f98b18f72f3633626893c053b7be6bfbd2366 +b2a2f6b0cf652775679b2d677048f2ed8c31a3269e6cddcc7a10e3e6fee89e486b50d9d55fbe452b79c4157c0270fb77 +892177c364dc59032594e7a6fd032286ffdf4fa0b9e3baeb37ec839faebfd2fd46c57b2c9bfe9977b59c93a9cc0ead1d +8ab7c0038a7dbb2ef200dbbe9acbc875829ecad4883792d5c6ce283de67ccd9aa935a9cc7b30b2bd9de7fca7bf2a9a05 +83745cfc78ca709835aa6c6a233c2b86fb31e3f9f6a8becf63e501f2841c4366fb7d131b746c9d3291afda714ff05579 +a723dcb67925ef007e8339dc578d2622d9bb77cfda87cca0088854a59414c02338752c56116a6c1281917842e8467c38 +8a098142da0af2254c425fdbbd0d1b1a17b2bd781391ab37f181775524b8563c64ab8a1602aee2ac6c0a82ba11a8b1d1 +b13bd7529a9b351c5d395c794c28bcb0a3167f1c992e8c062eef47be9be27895945231d249c73a0b6949daa295e14944 +a20dcd2fc2222eaae467d9f5db861040f58bcb991a26e5663ac3aa5e1ff13d0010657c5af586cc4621757add2b905073 +b818f660c3cc4e9f273c25ceeabe562c8afa8ff88529c26f2cf45ae6b2813cca5f350e3cbd56f6257c4df41722dabd25 +b225d5987108b24411bc389276f12509a45e86d5ad6b6d929af5274df0be11109c0fed329669a0acafdf3b0beaa8f2ec +91fcb6d04576d3c6bae947bb7843b430e5fb0592ae49b0a65dfa5791f4eaa4bf2c7f436c8de7360f217001c2b4e5c67a +8821f7a1424ca3fdc5d4a5606ad10dfaba6094cf36669fa9f84cf7617e50425405d14980780e1e18a1ecea7913cda896 +990dcb7f38f56521a70cb71bf4522649fcd46ac052c7feabb0748dfcac9f9c0f95d29e070d32af3cd0adbf869535e17b +b0fac1029fe2c1100f24e2f4bf10c7672199fce53513c7dde2e8d9b00702edf0143e0e1dc7ceae7dcc6994edc2422b6f +a514ebb1a33451b4915c05114db0b10168393613744df848b24e43e09f0bda23baefd9d731075198aace586615ac7911 +8b77f7953c2e67049fdca3653b8d8cf3f799677f79b954da02bdad8cc4d6c855c1c7c16b4f6f9ba35f46426ec28b2d84 +875520cfbda16ec5b1d1d00f578a910d0fc052f17870ba093e22e310bb07648d34817cc2b8811b6f52de535f7046a0d0 +b8c77b4be0b430851c4ff69e91cb770db1935d848198601393810ef395efab52deb9d5c6525472bab720273d5e0e7a79 +b6d4d437146671bdea62fb6545395ea3df39f1cdef21b8476b68e7a25aa7354f847740576d6c9f187bbae9941f0ae450 +95c642f1bccdb62cd6a2212dcdd6ff8d49aee426ca08b7cf3a9d15249d24a9eed5533f92a70c84498c0797f8a57efa27 +b617978047ed0f748c305aa7f30c2dacd0db00baa67fe0c5ce346ef0e6991dc7e05f18dcb2702467421f8390f27aa815 +86411c7a00b3e8b43bf22fb061b1f54ad9bbf632cd74395a478218389c0f544668acf3dd7726532d080ca7da9a5f8608 +97bf684a8849626c4710a6992f6c11f6b5406fd4dfe9e6aa502425aaafe9827e2c435aaf9a5d3d2ba3a4c0e8aec79ba4 +8b178e2a125b461d3180906ffba0af3dce614c64058501fdd35243ababf892d6fcdea4834ce42c25d5569452b782a709 +8ebed2c8a25c61da6a6a8cb0d8f5ea179e28869753eacc728f2c076f7aed8598cd3aa0981f120f9e7ea55b3a689ae882 +a6f235b8e655ca3d634740b53d8c0a757ecc75d2b8838b7948997c1985473d01943d935f687b86cee56cd47c8e773443 +a7959c465a9646908b9d8032a589e41a7dd999f2ffc54bb42f22e5f8a4d8c493a31bcc7ea2cac6c8dbcc59acace7181b +96d0532df2e12da20a57cadb6cf5f6c4ee1aa4775629358c25f1d51677a3e96d1fe3b232532324b4f02f941952d4cc68 +90f493473d686b639a30d1ddc9c72eae6e983f1236e162e58e967a477c0654973ea2e1bdf4ba1a44d7247bc1befc2cab +8b2d87876d9c4085102a07ebb41c565ba69acab99ffc03efc18f20e48d3f3bbe4fc6ddab9c78fe479d9ada80504d85ba +829a0fb3200a28e09cacd6c5346000e7786116ddfd898f37dfd17bef454a8abc0fe939ed8735c00769f7f2f33cd4f906 +86194ec9e88ddb7150e8b03e7a535b6e99863fc6762835601efd03615aa97aaeb413cb210e86035086ed852b39c9d019 +b02efd116a7189cb317ceae392bc301ae55470f0489fa89934e182aeb8c67e280299b975786fe9a470bff46827defb9b +87d7c3903bd22b12d815506f150373f518d47dfc6e5fd74347d88b518124c9923d1e4c98defeb3a45d53d50b423e2175 +a1a430406b28254a7d6348bc98e697e9bab43839aa05d53faee97546f84541ea0b559162619b2045182938f69bf61cae +99d243c226c61c6697fb3d2594f3533fa5dfd7cfc87107908cacde337d7a077fa5a9dc702d26081b065edb1227498e65 +800ee5006ab6217161f42db0cfc552a81728bb4fbd7af6e4620ea099a65ef6664184af3f65a07fcec7e965529c5b49bf +91bfd307579cadc8f81009558605be3edbcb8dbba271475803484017f40130b2b216aef4f620d960193be681877d3a53 +96a060459dec458d19a6f8af6e49dc6c7c58c55dd18915c5fce5e0f4b4a422fce3b9632f6059388fe760289abf70f173 +9921a37f3e657222c7fda3588418a9071409711d9f1fccede7494429f02a45fbc52d79fbb64e9ccd518f60d06d0520d3 +81052b0d15773cb75975ca9230ebb2579700e489c7e3f07cd9cde206fef38b8139bd4976d2b4a7840495fc645f96df03 +88ac37ba66d1de5e23878c992e4d54023729e97e77351f50dc5918d738b5a73faf1dc6feec7e85784761836ba1c6f778 +ae1e6072c13060775f6086d1ae1f88b627ffcb810fc0e0e97deea1f3a15ef0aaa52a6dce2563e4beedadc131af2a8281 +8b60a340f5e4f90badf83001b495ac9f13974c3d2054ddcb3e6b8ca99dec5cd63a263e05c282454191ab2e087d5a2911 +832e2d56ba69dbf817b2b9dbd25c1538d5b8dbf5d9bc05e6be85054a423ebb66a71b157e166e0b9444ac171b34b7ccc9 +8586036fc7dde1e7e3ecb61663130c4529866ae9f5f5095b9fccd24a4c70eea899aae5f10ea1ba66d1665b2d83be35b0 +a77969453b5c083a207913272b5b69d4ccbd8718bdf54be8fbe11b4bd0a2168aae3ba8f9362afa69c0ffa28d7e5a2340 +b7fe9568c214baad0ac5f83745611b481f744ec1c4fa78a549b180dcf79633e5ba75dc20055012a13d849eb7a9be57d3 +b01cad1d2a6c51c0ce88243d1f52f95fb5ee315a905079688027511f0c4ecd0563a3a81846709d272fa5ccb9665e8043 +8eae0a21adfc569aa57237654021c2bdb2c6f0f52ccc90a126682c21a1f9413c63d285f92b2b2f8649150a9284bf70b7 +942acc947192b5f3cf60e92383e5d35f79e7a5904e8e9fd1c8a351676c83ad29b0afb6578d555457cf909f8f4d27adfd +a74e092f8628fba9abcabc27e2e9f3d5a9a941dfe50a2dfde2ad179aabc73afd196676925c2d98643ab8b3d02bdb66ad +896159daa2afd757cf3f9d34af248ad68bb3c62e4c9ac49919422727479cf669098f270b9e645607a7d11adad4c889b2 +a428d8370813d78e7a2a24eebd36e9da2f8bb3605e5a39b5fcda939b531c35a8ebaaa642ba556250a37bddeec90326fb +a5fa04eb60a1d5ee9820e78f42f7be15e1c02757b539aead995768c6209684d6c183c71d282e0c12a4c15c03f9a89d4d +93c77d5d220e40affa7269a6915c076c9aef4db552c643ae5d560a79c955b491c6346ca4cf11cbb7fe1894e28d47b065 +802e605d2de745eef6981d88e7a57ef4046a2062725e8080995374cea2b3273c27f35b7774d0dcba014710d8d6c501f2 +82f7169e6ec9b3e2bd450f35ea2e66d06bcf900acf5b73139677b48e078ce2e16599103027b2326770c99c0a690f2015 +b0c8581879439f9b997551233fe2de71aa03604f9cec37a7b18c5854342d9b67be468f3cac4bf6f64fe8a0066248c498 +a3f626848a4db6e9fb01cac90d3362ec521e969ebd5228af694ea3671061476149f13d652942ac1e39f65591fed740f9 +88a8e759b9cbe16a7c16e43f4afa2de6100d2eafa4dee75ccd653ec38c919013d0a6b35c1ee1eaee7c1985b58bcc9e92 +a3d5fc7aaea072798490616552d947e95f49cf02a420314307aafb555287ec607d75589ba24b009cd68299dc6f7942fa +a809cceeb84f9bcf3c3ddafde3041e7bc3b1d14df8830ab849002176a0725e6f16f70774d8962cb0b8ac0dc43c4ac66f +b8f2e46c031cc8fa160a08c2ebdfa85345ed14771b06daa9636b0e7792b7fddbc501dfc85cc626a01104a43a7d3230c3 +b5367e2a521c318b802ce16ceac80c4b8139f73ddb10ddf38433397cda70a86ea1f051cc55626a4e99d27f30f3975ff5 +96d963660121c1441cd13141279cd371a6a0aa18b6a20761b18df60aa9c14e13489afd83695a0921d5232efe72045f07 +80818d492fd85d666bd91aaf6257b86527fdd796773c793407df1d4a0f91d74649a6bab4d15155c36ed4c6e0a32c5636 +931e22918905fd6c230d3d867ea42861f3074d320d14e1929031924c8ac209a5c552b679b24563bb12f9749b4ee983bd +a4de2c333e74ed9bfa3c0bf6a0beb90427abd9aa4221294cda74331646b58ef46ed57cccc8798ba2b9309894b17cfd69 +883881554c1d88c0ed8d3b6dec3d200f6fea69a77ace3e4d6f86b41506a23724b4394ec8384075f9c75c3868ba8a8e8e +aa0539ecf6ec9bf06f24443027f8f24b6b3d8c5b2084248eecd4bcad3c9a69716e1a0d01057f09a65bff1006ac5e157a +856d74d44c943c9e809b42dc493dff20eca03cb0cf5ed45108c69b1f90d8592a53ae8100e99380a274fafad23e74cdfc +9188257446661c88da093b7c5ce998135913f63842d7c1586065377b169ee35b062d925367fb9b909ca971f1188667b1 +8d3aa57cdafbe998938787479f5d590c1484c6dbe94e6c487e57a746ef5252be0eaa5976d6270de7db64b6b92e57a0f7 +b8f4d6997240f9eda5aca0c43323a828d1563c491b3db2087f60ac4120a3fcd06075fb42bb19d0339ab5ee3fb7db25d2 +ad247ea94b8ae1e81eae4c9fd7b39e6601b53cff47b2547ff90a3cca87192eae28408082774a1fd14bf9ab459b7a4f1f +9598598070f8bdbcc49056c40971e673726cd8c1bc4baa0b5124dfb5fb750e7baa7a7df18eae2bd91955ddcb1ec67955 +b874131ab1608667fa60ea29092d090859eed1812e90c609afff96d79e82c5ba546f617f4c96fc32c9bba97431c1e9af +b00750a9cdc75c2a54f0d3cc99b0fe02300754f25166f7ac85ff41ab5e9cfcca33a29be76a480f12a2d410c7cd5032e5 +84b5bd1c90bb6c66755b28ba4af493ca1b0c3a4df9f436aac67d2e07289053f925cf6a149a84e74e1027dc8758150179 +99caf64bd9d193ff306e8ab5da3f1bb2a190a60c3a82099b8d03d17fa810dc53d176c21379f479e828f60d25beb3ffd0 +a8fd9de502f1c261d5733430e5a18d8b7892a98c9529a016fc2ee53892ae965dcd9c75850bcda4c7edb980b8d88e60ea +848c02cac636e047028a3fe8c1bf4066fb7591b96b0340f8fbd476ff01b35fa3e37d309333771a134f24800e5f3f9289 +a1eab1a06dcca3439f0166441e7e7f2f5b56f5f8aa9f45e411c561f556e0fb71c514c06c26ac53b49a576caca5faac3d +aa603f970dcbe953e700e61c151182c8d32cbbb53ceef572ac93383db33a4b098b5c7b267e42d514ca66b740c0925efe +b55fd5301bd700ddb0b4f72fabe9a91ad49759506101fa802ed1677e9553595aa4d2c66f7574e78d21ce882ce0120ae7 +829137bc4da7b4886d3d04d2c39cbf4b1dc40c813ac1adb425c7b9abf9142b516314cab79c68454df5d71994ce416144 +b83a3a22735001f783dd48a01c4fb3598a51ff3987e842b8045c71c035b9e43645a55254ca5911a5676ef4a8af12d056 +8ca8d463deb13f9eef5e533bc39efaeb0c15631282c5c0deee1673b0053a7cccd514af09801dd6c158caa159fe9351ac +a9ffb1427828f3c456b9c8cc50782de1ab0029b9233a0fd998bad0fd014d27e15c4a32d1e16ad41bff748378b5abdf49 +9627e29f725ddd86456aff813976bbc4a836f4deabf5ad9f73d1a260ceb30948824df9c8841e6b3c529652202be181b3 +b52c988647fe3d9276eed3c262e1044f57fbb116c64cf4f207235c205b3fda0f3d789bf90f5217401b468d85fdfda404 +833bbd6e2924f5c4446cb76b881d1434a5badce9eb9b003f85d076e297ad7ef45b822069fe54d17427a348c3263fb838 +a067a36352db6f82a116cb87d3db5f60b18576852409e2076cbbfc7843af78866313a4969385a40271051dd195d51116 +902b99545971f9a103f99d7399acc347ac46fe156166e51deefc0e92aebf5893460c69aeeae11f5af9f49418e289ce6c +9206a0e9ce9b9880f29ef0417c96931985f5d83bb17cebdbba4ff2af81a3d37155b04649426f698aed372e4f669599e6 +b54a5d7c976e45c0b1d44433595eae9d1ae9aeabfd58cd5ecb0c5804756a7b01c9a517754423b4714a3695533a3114c8 +91b612131e84580ece228b81ace83da0269b53f94d3c02a1a0879ebbd81bdc252064b3d03a7e140b43a90f237d9a45a0 +a6cead3b8607eaeafe37135bd6de8fbd16f806c131eb71c8d36bfbe295d45b070255e50dabf076e2c3f6b8699be71d6a +931da21e67b11ba6ce438546a24d063bcd51aebe39b4220a78d9c0aab88b2d37969b5ef3502d835507f9c8d6d006714c +8fda408caa9daf01122a2308b7b9d328f52e1e2f138a8bec30492488f4d710e5e52524a6455a3a2ae2818ec8a610b650 +ad8ad5c189644352d90c462731c46145410e5adf38682bb80f95495dd64d9d13782537d68690847bbb06c6be7175dbc7 +87bb5cc466ade60feb0961421c3fabdc8a7e20f11df8437bfff63d3f8bd25305002a396c9d0fa4fb9a9986d4717f12c4 +827cff72870ba00c29064a7d2b4973f322d6b6de7924c93d8bf8825e7a0e8478c7748f90f5c716bf83c55b2795d315d8 +a225895a8e94229776ceb51b05356291f2dce748be17a60d5aeb33ef8507c368bafe5d1d6eea927f28b9d1422b661b9a +8e011323ce670ff51c964241a6b72e0e0ffbb3ff9bb2762492323fc3a4abf4718091be0945287c7329850e4f74462cde +a2c03c2e5f4e9d3ef361f68b188451994ad1b24de9f323370559c8abfcdc7bffd289d92e78a5f6b104b0a12c84dab2ef +a22b4771116ce22276fab1fec6826610707ce8a342f9f60b079c4e0259dac3cc41c96c560dfd0ada6edd2828f7c0e8d6 +97c17441d0af9be83b42097aa8b7cec84a253b9a2b957214b8fa93c26d2add46144faffa7b8a55312059b10690f711f1 +94bdf348849f31a2737cbae5e5848aee711067bac85c11c2e68b44c398cfafbf3493a3226cd1ddf7a916e7613fc7b6f6 +838f59c6e8469a8ec6fd40b978a3607439aaebe1e50ff707eec72c0b8278af05b477bf12a384b56d03e3d4eb91e56f67 +a1940f0db58185e2b3aedd2b0bc2b73b4a65c68e09b046f38e9dcd4e13c94f5406bea92635190bf315e48ec64eceef2f +b2f4e0ae44e1f1210a91d8f280f17091fa994034ba8c991583f8182a323e9b3001a712e3584fc2d64ecbf2d319d076b2 +9342b89c721338d02c7854cd7466fb24d93d7313b6114ea591e6607439c8ddb911d1cf35f01898e9c557982bdff8f9b6 +8583fcab15be1dd14d5a415f4b14d706c8c62f058500f1344b37730c8be6741779691f87ded3cbcf6516468b373cafb0 +8fa9587c7989646571ad9032f34cedd353caee14f5be5cde1e9e0a1710f90c08faf6fa96a60e1f150f761c9c8ae7417d +8d9ff904cc08141f5a9879f5f77dc600e6edbe859082231a4d819953890199bcc5f940b730ea688332f07e5279d49e1c +b5f82b46e5ef9a2df8d144202d6e2e4f3bdae8e2048d2af5ea7deb3f722fbe6d370401954e74ff0d8cb1010ffb1f38d5 +a3b5b57d435b06ed70530e060002a8fea71746ad07d969ca23f22b5e52624527595b6a6d54b4e953fb7b7596bac378f0 +b90f89390df6d4b7879b915aa3c29b8d779d035033f8873bb7ac54a14ec98f0d08c0e3bf696e2ffa7b5730d736f571f8 +8e81e371b92887e43d95c0dbdcc9575282b26ccebdc8cbf46587e4f2a83b61e9bc0c6d7d1f114b9d21e04fd6c180b12a +8d682947c51dffc6e0fe0a486293c9ed121f441805168236393087cf62f2a429cca60bf0e472564844347d32c6bea27e +a8341ec7dd189fa7168759240224192c58209b53fc961c18082deba217928c399bde08ceae42bffd37c1135b4d14a845 +a94bb076dcc5ee5ec82fac57c5b384c690df12631882bd1b960e1eb8c04f787bc22b7bac315b9dc5a8a098f17f051a0b +ab64e1c6f01b87706c88a3bd974454a438722768de7340b834ccf93ea9880c14ee7c2181432acf51f980d56de73832ee +b7b0058bb724d879e5ad7aed6230297c54cb599ef659e86bf2cc84c38225899fb388391df9b2e6fdf063171937fd8c72 +ae856f4fb74c27cc98b67429186e7df4feb01278cd57bfd3170af6e52e0a23b9e926bf9565a890cfb4ae8f2d590b2cd5 +804b9c6702f0596d328f92fc1ed5a30a7ba17b9204524135001b569233fc4937035031d079f52fd04968f37c24013898 +84274ed1af6bd6a968583995622b4d18c6a2bc703ce0d0edce45bb736529b4836343dcd11911a94a134dca7877e6cab8 +88808098463f7505034c3b6328c8a08186b33f7a981c08376e429dd64b79b97753170531ed078dd265ded4ec0a1ed8d5 +92823bfb23a4eb84d3759e7d717f0c8641ece0927cd2ba8c728c26bb35df2629a838002f353c8d3d75eb19520aab5f25 +8db36bae4d960cdb9c51f419d7ddc81f372e56be605bc96a9d4072b829f05527c37c8f255cc6115300a2a0d2e6568d89 +a8fcdbd7f3b4d7ff04149a209feb75e97149e7efceaa42d66a6b8e432590fe7bd01f1a77fa8b47108f670b612e33fee9 +a9f4c53c62db7e5dbdea6918862d3c6d24b5bd8732a218edf0ba61e9d1861182323d8ecd7bef8f895b42970b492f6e40 +8b95bc7f07818f4d7b409aff8da0b2c2ae136cde386f53a71565cae9fd14c73c13cc1cfd79c0f97cd77839fb738c5b9a +adbd1d11adc756b51a571ddbcbf4392415231ddad93da09acfafee03a9e4f9e1ce3826110619e5271feadfaffce3e793 +95d327c8bb195cdf25fd79c98f9406a6b0316214b1630ebcce95bdaeffafa36fc1accc6882e0e5d13a8db5c0f3c0e61c +8cb2f1e2fb25558869afdacc7bb866544cfdd566cefcd048b48d458a886130bd086ecb7600a960a7f2563c61cb326510 +b3aa8c4bf5b933d89cd74ca7f7176d6624d562d7d58b041328b49d7562a30b489cb606abb3c49e85baf04c28e9cd1f44 +97f9053a85250c420599827297453c2cfde087065b823d9e43139e6a9cac3a2ec40a1b6e2f0726bdc870fff215462f0b +878d5dbe6b881389c2ca126ff66d87127c9aaa3f62f0d2c1ec0ea2b279ac95f8a06710dce166415db227655e2345a04d +b2c33a6b4203e3ca5247f0890e475518317ffc44cfbb1da9a1ba02114e8b752bea618050b876de5cf3b1906140a64471 +a56170c8313d2b5541a795bea9934d4425b185b5c409f0484df6f44f0e4bcbf50b860ff46b7245cd99c1cfa8fc1965b7 +96e2b658e2876a14147385fc423d2702a3cb76962b6b437222cf9cea39ebf4bdc03bbf434b747866d4bf72b4ceefa639 +89c4a74fa2f067e7ae49c84ef782c331bcc9245db7e941804e2e99d12e987b4d25cb827778ad4c3566c4fc68018650b6 +a01d30cea7d01c80ff26650020fab02e78fc3842e2398a81b44b21d58d4e9816166ff4ed2418831fa995a28ff35cb6f1 +b960c80b55a8845bbf24bc3f23b0110ca701f9544ab6a5bb7929330213cb471321e55c390ceca3e24bff69bdb0d331c0 +802c5b13f22be7be0e5db11eb3be0f0ea7f9182c932265060ba05fba20ea093dd2810d3b969ee3e387e60fe6ee834e8d +92478f88ef7435d15e39a97916c736abb28ea318394b88678fddbbaab3eaf31776110936abad116a8ff6ca632dd12043 +a6d3da0370c303001d5ed99d1db8bce1f26b0e442f0f042e36db9674e92dcd6e80465e772f1e669f99221caee3392fe9 +938f04f70a8f947d6df2f0c0e9af3cce0c06edbb3c131970dd60884fc0b0a0959c504a2a36c3ff76dfe919905671626a +a7117e55224230822e9983df2132347eb7208cb6798f291df926ab51e04b1a1f78d5568c9a8924ee6f57426134360f20 +b91074c77ad93fe48dc2b10c0c5a62ca3ab7d98345b919c52d84a9dc419b59fc1b267e1c2d4b2e120016ef84bbdb0cbe +aa175c6b6edf02fe8778762c9575581c0ee6efc9dbf99c291a41444a23a056b893be6c45333d907d0bbe9fb0eef84d08 +ad36dcb4e2ab425aa339ae464b038d550cb11186741dcf257f1b8b80ed4f32ffabbece45e2dc1525d4c3eeed819ea04f +91cb35c1ffa9cd5aebef523edb8325078da3eb5cf9e95c675a76446fc7692aaee6f949de064ca2f3e0f082cc3fa93e20 +82622f9410c143a86bc4d756b3c7b324dc295231ce865de020d61cc0868f2c150a473cea3a5b756b36771ce1032415a5 +a5c29996ad3a53468ece9356a5b4ccb68971ea1c89cf39644f1da2d4a477c2ea99bf791ef902b87c225d8c53d67c4c92 +92893eceed1af34fa92b23dcbab175b6a0188a27dbac9ad3317c4e39955a763cb383ab13fb1c519cde311d8a4d12e8b3 +8a093cb191b94b0200e38d31955f9d240e2be1edcd6810a2396a061f17c3ddc9c4f4d56766ddff4e121be7110e03b869 +93981473df0cb1f4b47c7d9b64e3123dcf1593845b401e619f5d7c70b5dbea375d1ca43fca65845fcf0a6b2e0af43791 +a6beb6b0697070f9562910add88d9ba91992f8da127b27be81868b1596d1012f09ea7ed601b4a6474c921a1a1a6d866c +92026b1ee30f2ed61c9f30337c3356844217926aabdff383c19ca3c21e0bc49811ca5b308012bee4ef250cfae1615800 +ac0ebaea6d35f84dac4ce648af096305ba68a7a0aea0a11ab2fbe3162075444a158433c98141bc92ef3b3400d6deb46a +83046f482dee24ac3ca83373f0d1b82ac1c4beda0f229a9011a81ec659ff5fc1fb105e219975b5c744308c77a24f71e4 +aa5a312c47ff7248dcb9c6ffbe5a0628ccd565c07365c4413734d415cd4fb35772622ed833862dddff520a67c509c6a5 +a02fb88805c34018ac33582e19ed0a7e4616acc3dd0867e5f21914c2031c05c6dca30b8b35b57c2b137750f3878a6f8c +a60528f1f14bf0c496491d46a0fbbd6c343e4eb3f1631e92f96a3c5e5c684091aabe5801df7a67f7c6dfd1b0d35269d4 +a1fd8e7fad8ca05a340c05a051bb0eb4197eed345f4104629a9e38e234b09d789cc5537024615feb4a6177d32d39e39e +8e70e36c1aa070815440e19443f1f04aae23b1b59fdbcba43b47b94a026c82c8f66c5dfe54f826f4d95ee1930cdb8008 +8234c1969fa7e9079661e4ca309b71b1aaa10f4372be0b963205c23a81f5a3d52ec08ba9ff65b37f832b52d631580d61 +a18cb4134127fb37c4abca328cd0047378a2e1423490af2bd3eba9ffcc99ca81a3c22404c0886f21f65c7b93c41d7981 +b46fa45fe538816de776eec086e040005706cb3eca097e290abfb6864e745c879868aac8361894f3c3564373ef9ad55c +b96ca43b96c59e95439f75d1e726a35a9362f0dbd34963b156e103e080a8126a8dc3501f9fd541ff3bcf4677f5c4a86b +a8e8c87c7301613818d57387009e601a7ab5cbdc2890f63d985c30c74f9cea2d0584c116baf0d9cd5594386ee93fc661 +b47e4f1b9153ef0981f813948150f283b47a7346fd9921d51fe8e4daedaef78ddeb4fd467c2ccb7cebd9816243da1c6e +a370c202a99c8441ffe96fad0f801086d4d7cc7b960f6e98cca29ceedf492afddfd0f351c9c4d29ac008bc255ec1a2a8 +8f5e6ce1655d1c059b006174e3f5a55c88e1821c97f9702ad8e8455d46c2a83ae4482f2d43edda74a835686ec45a8a15 +a30421e694930a3b65d397b2720d5f8e1eec2b6e2bb5a28d3f9b0a84db9aabd83850268bae64c2b10e313cccf120151b +8abe87163046f7a9b18e2a3c0b66e258facc1b31431420e0b70354b7a60ebd250a784634a76692e7d6f4330b62114945 +894f033cf077d4eb312e3258d9dca414356271abce1d6094ecce6d018c5fadb1c15d8d69451574ad0701a2876db191c5 +b0923d64f88ffc872654e1a294bb1af8681689c21cf08f39afe51448a68e60a9a0a74ccce9969276a932a52c07d095a3 +b9ca23b5be8725fae7fa710eefd45522889c50c29c26384e00b78a962384f0aeff9d15cb5910e9565da12a577eb7e5ba +b242ccf292757197a9f470f2d80ccddc48c7f1235ba026bc68a93be2738bc968e8a200aff3e2f4807216442eb3fc50dc +adc2c3b375b308524b79a024ff87d122055440643fea6fc0a651bdb312c7cbe6a456afa9d342bc76446d77d8daf08bc2 +ab645955356c2ebf2f3df9da275e01daf0b44a52afc309277d6d9ad1b05484e5ae0d9d41ad485fe481e5e362826a86ae +8de96ac587a4449fcc8b7fd0a51b4b5185d9c2eb3434f94cbadd092de1e26b0f6b3f7b15a37e8424b1429121ddca0ecd +94c70ad4e9b871566f3da98170b665a09788d421818299857cde0853789fb943cbcf7d4b2c95246ea7b72edc56a8e36c +b2574be63497843340700b701d5cc8be6d23125bd62058802ee67cce1f3b5f5602b27c93fea5611f27dc695ac563f042 +869ec89da7850cedd88bcb3a50a15cece233119b31b64a61bf6b2310892ce42d8b473b584b11e61db29ed24ce8033f83 +8fbaa269da8e28e9adf4c1b08f109da786dbe9cba871c32eecbfb10619b7a5d65a26f9bb33e201a8ed20b3de94003fbb +8bf7a059c37242caf7f821a6314e4e4adf799e0dd86b37892a7172598892c07272acebd05b534755c57b51556b2d610f +b4e72645fca459898cdd9214892ed08b5c99f82049c0a30d72bac0b9717caa9c6cc16c3dc7aa6ea4d42dcd2a6c175df6 +a39170da87a3495da55bbb9701c5461f3403447174ed6a4af75712f7ba4ac35f51a4234bc4b94da888a0959ee109c0c7 +b45675b2774ea7696089dbf7a0afe6c22e85fd0e4ef3db508fbaf96c9d07f700c991789206da9309fd291be696357c5f +b52899e3e3f6341eefcbe1291db6664bf3b6e8021d32fb9c3e37b6258a35c1da927747b2ce990937d6f4c6c3e7d020d2 +84e5bdb3dfe19700d79dd3fabb0159ccfa084f7288db836c855b827613ce8071067c8d7ac5cc2b4e88ed7f84b690f6e1 +801477d200b6d12fc6e0a9bab1c8211193ab06e44551e037a9b4c36fc2d4f67760b9ff4eba9a3bc7b6e177e891f64ff6 +b6b71a5116d3c22af26a7530f535e9b7851f25a84e562a8f17a125d55b9b3fc1bd8cfe65bdcbeeb328409521e802051c +8687e21c34d7804c12489d30680d131ce2133e2981bfa993afd8a8eeda958ebd5e6881d342d725338659882d9f21cf98 +a024e97a7c4de32b6383c34431994abc533ecdbd6be9bff836ec1af022f5a86773bf345c6f33273797a61fb70a8fd5d6 +83f784f095da20ce5b31f54d6cb14b32a8a12675f0029289c9cd036b7c87a8077be2d04a62618685720e6ee69c875e97 +b4e9dfe7cb9d9efd3fe00d99ae5e48769d4af4bf43d4e05c0b54c9cfd8bc854de96b8d3ebf4dcc06b9dac66b7471a0de +a08b79f9d4673afcf7f38b57f484f88feb7c908f597663a2417f92c348150c2be6b5603f914eba0d9d5bdd4e5c5572c1 +b0eaf919589988798cb01ba0610cd1b7fa3c08715675ece8ecd5f9ef6d5d7b2c4c8ae1ea7dfd202237171aa3e6f9de74 +abff99a98baae4dd0954052503ce81827781694a5ea8c1149f96a3adde75dc2d630e138598cd2ae7fdc7a654aa17df8f +83e369b8680d8b9d995222b033b4f4f3e3b20e782113c941325c7fa9c742feef8747e4a212d9aa23285a259cc4faef8d +b16d5855dd2716613697eba36e2fae0872aaea6999e91cf6552f93f9a0b85ed4f6ff922a91b50816bd6cf8e7a4513fc9 +848373db600e32e741aa1d37726bbb28956783f89ce2d781e95fb1ee1adf4359968a141678af268077eae4c25503204e +93a0dd0fdac18a31875564505b4e28f9e8bb2915faae666538597731ac56cd77f23f2456461e2f672983fb24ad91f6e0 +ab1ebbe49fa56524b564bc2e43784147073e6ea5d27a9540fbf2e04d0f87c645ed2fd28b3e4982cc4c0af1734ee47a6f +b3ee30b733839edab6f61f0738e3f4afaeccf700d8dc7415684f193b36d70d07acd5780cf539f12e0fbf8d4683be773a +88388f2cbdec47a6b3ae460b69eb0d2130ac14de950c22fd86de03e40d02292bb93cebe62432da39d509c1289f785fef +9370c41a54b68ff486b4cc6329c3a851716ebf1d088d77a6c56dec93a18b8a77b596cde74cc17d2adb2b2f411a2e4bbb +b9083b60dc16531f77b05a955b51a237a8f8c0173d72c352c5ca441b55abbc890b14937e457aaec4be5cbbf80cae0099 +aafff8f6c6ebaad952c65054dfc7c829453ec735331bf8135e06406b7a9f740c9a200dc48bb2175516b41f77dc160121 +b43d31fbbaf10526809e9e5bd8bb47a76e0fabd7852ee7744404559ab89f0f215ff518f3271a6aa972a459cab82ac558 +b581ede48c6ef34e678f91dc4b89507413e00e70712e3e8c32a80eed770ec8d8b98caee9702d068aeaca6f704be57bd8 +8cb0a137e68b001a5ccac61de27cac9fb78d4af7b2f5a00b8d95d33ac19cc50c69e760c5e0330a85c0ded1edce0fe6f9 +b947fca07c7aa6c2bf13048275402b00b77b28f1d0ba4b589fbcede13f93b5b931c588560ab8ceba23bb8e748031b55d +81753cced5ff819901740a9a584334e355b497cb699f0be5a52cd555a4c9f149535c7bb355b54407f7f0ec27de6c2e19 +b3d59273951ce97838c4853ec329782a255b5fc7c848e7992ded1be28a5ada7fa3254123afe32607b9991ec6e0659b08 +86b253de246f82be1cb0cef01e87c3d022ca1829d2cc7e6a160a5afbd3ca6b94d75739b122e3bb16f8bde28a8f3223ba +b728b659fa2d8487e061a37f7d14a4c2d70cc37497a8715695d8d332cb274deee2ce23b9b5f6a7408516c02c3d526a49 +81277b46d98848a45abfbe39842495659dcbb80dee985a4fc91d77d52b815487aa8bb455f411fcce4c3879c7a075a93f +b05b6f1fb4a6e654f0ee6b83e08b58b57059bb0b7c490405bc8d963c4a2d6be39c558917977e554e1e9e3169961cbf3e +88f75fa7d016fb6442551ec071cc1e2beeb3ccd213d16d744f573a82f5d70f41dd1b18af71d5f9e73d87f2f6b7dbe889 +81a46434f1bbd65a661a0ff45a0295b8fd8a42a7969c5953721bc98698b64bddee3f806876d1e9983063fdd0c11f99df +8b4f6d33c510a4c9c7d623d9ae0c9aa631fcb987704726b2a4d8519372123bce3c439202f25b5b47045ec14ce39a21a8 +8d5112b330fb63cf6ef3d2164b404c14ff9907d685015701399a260951912b19b8f270f869df317e9050a127763d7980 +aadab394e84dfb82db15ecd2427f39b62352c3e1647c3bcd14fb24ae830ad0116f0fed87ddb63963b424a4741961386e +81ca4e5600d00a3bda24cbdea7a532a4cbbd893c10e7ff10667c15ffa8138b91667abe5466b31a3dcdd60155c48538c1 +ad943af1b8a5fcfcf309ed8f2f916339f254cd555c71a407a47365a139306286a05a8314e1c70e20a65fccd75d36fa12 +b16597a0b437060a390467bbfab94c0bdd695ae898894f4689f939e30cc2119cc08ecb594546304adf876f4e275ebcd9 +a44a4e0a6693be356065891c27eefa040a1a79475be53d54d5fdcea7e0668ff9b35f850974000ed119f6865aa6faa721 +adef27d1b6e6921f4eaf69c79e2e01f5174f7033eaafdd33edcfa5119af23f3a834ffe1bdf19576581b797abd1865b34 +90c1e9202f3ffe28f8e1f58e9650dc4ff4dbc158005b6f2296ec36147e524b4f2f87f8aafc39db5b006fe0c491c92f45 +ac817cd54288b6f7fe6338415344fc9e7b669414051631ab2f27851c052c044be06bf7235d668e194bef695923256368 +ab14944ef653a14456d4ebc12e3196df3f1b4707c4e50b317b5ccc8ca3a0720f0330609f0e7e71793f6ca01583f38c70 +ad5353f2f380837e5ffdf079350b3d42935a0517861d03af98db5ed3ea8501abd68885c8c65f5a66e944b1874826a450 +8b5583863f84af8443ce8970b02e26cc5d959e47efbf8a66a54106ab165f1f76b36423aee74c7b5402fd1c4d7c1adfe6 +b3b46037eed9fc30e4f8f0da8bdbdcc40a38e22e876ce9fde981883017854aba82c18eb00887d92ad847d30082fe7271 +98a2b6fc90b7ad172e4368c1e54675b75c8bf2096d91c9f2b60b3397d3be3b705aed5389845dbd68f0f84438cd0f7687 +b155e800852a5f90a2eac69cc4483428da1dc2c31588a13c924e60a7616ce9baeb7d4b829c772b260277cadd8ed84719 +b8b92c520a1302b0cf7d993a52e1dacd7f27bda9868d59c55687d995ae676b7070af4c0792a9bc1c2635d44a4fee01bb +96dfe9bde526b8fc829eda825f55168b88e8f4e43d4d708cc3060df03437b46e12a8ac70d7788aa75760f6294d3e84d8 +a3fa66c54e2fa084ced3bd838614c6c33042f492a5745d167a723c60d5e7d6020ffd1747981a23f8b68df21ad8f0fa77 +b573ca10cc41fc04a642f6f62c355a4fda69b94b8e95dbb02fd1ccce4bce1191356e1fd66d372159944eb36a7071f005 +acd0a1c9abddfd0ea223eda1722aaada362d34234455bd1c6be115d41e535b16f12ca428da7820a757fa4c98884a385d +96f242eee99c4db383b8754fa7987c0c159652e1866faec905a8d3f010e0a1ad05bd77b9ea8dfd653738959180f58430 +9215a9b672a5d6e435e0e0a45156e0e20f75cbbdf1d14940fed3ddb63d433bef643796c7a4fff881829ebb2b2eba9460 +b8ad9bfceaf08dc5a874387219ddd1170bc3a5e25ed72d321d59ae713be5ddf9fdfbd3aa7ab163be28dfa0dd14614e19 +a19a1050590bc500b32c502f393e407abc3d8e683d6f6b978873aff3e3299b18b1f6b59e2b0fe237d819dbdfcfdc98ca +a6870fb11d4429686e52e1f44c8dcfc7ea24a020df9570c021578dbc1f9bdc8cf797cb3a72d7fc52805dba35d59f2cd0 +a7be733b64d5c06c127bd1c87250e42bfe30ca91ed8ce51e0b6e377f454e8f6fef7f99bff650695df2fd10c375da349b +a1b97145dab30330eea2cdc8739b2446a3704b64505fcea3dd8a9b4a72edf222e98d967d6fd7f76794acfd97aa091065 +b2127049907d2a3b654d1c940b740bfba3dbaf660f86ea79c2f909af7c9fe2a07a1caeb1be12370aeffaf8faa50f1582 +8a207701214bb28e99b0784e9228b1c34afa701966267fe7110f6f29f5bb41eaae6cdb98844d0400787978fabd224de8 +9925147a383b6f5f814520220ffdbf20b214225882c3ef49b1a1ca677709176ec82466fb9c4be2dfbe5640afb63b014a +8416ad93871623fb555b5390b80de99edaaf317350cc0c1ae9d54d59517074d40061f315cce8ba2026d9c1e6f6a1009f +a315f943deebbf0a2cdbcf3f8323e215a406e9cbfbcc3f6288714cb3a6befb1bf71b2a21ff7a2ec4731c65044c45b6b5 +8213e0c2539c24efd186ffa8b6dd401ad2233bc19166a0623b26dd1e93614bbf792823f5599ac116231e2efde9885709 +8e5cafd2f34a127a4a896f05e4d929eef06972a1826b3566446942198df26d62f7679b987db2b3765d9d8058b1cd85c2 +b5302b399c9cdf912fd59007ad4737255552663b1e56dbe64a7b2ddd88d2093c73ea319b45db2dd49d1e03f5bef1a0ae +a0c2bcfbed4b008e1a56e5d2f2419aa59d7dd0ebd990f1c18588de702ad0fa79f445d69965fa9381e700eda13b309378 +80a44eea1ffe24c26b16b8e2e70ee519258b9ad4b3e83cc4e5cca88ebc48d0160066f8b91d0581095b0de2428390c8b3 +84a90cb9c7d2f799f1c4ed060387a4b793ab41c5c3eaffd3b60face9b9c3bae93cd2017283bf3de1e3dac63d0d84dd42 +81d22febca276a05ba9bbc5591ee087b0491beb35b4d9f8fc0d041d642a574667ddc57660b20f5c568f7d61fdcb41bda +a3ac965ac27a28e102a439b74fbfc157e75fd57620e4c0750a466165f8aeecb2191dcf8e656f7525aa50d9c7c69b0b5c +913c17434ff0d9fc52e2ece4fec71b37d4474a18f3ea26925c1be2b250434d49759f58033ba0fce1c6862c6197930dc4 +ac430559c151a5e461f67b49c7786c97e1653fa8698e9759ddbdd99f5daf17fc5a012ae6330739440880728f24eba7c9 +b10d8e9f8aed9361b042d1398ec74364f7c7c1cc5c7f917060572761138bdbe89bf409389ee3879f93bc8032dd67b308 +937271005a4cc6a6ec134870c1b56471aa84ed4f4af1b3d5f334bc0c42762fae0c9a6a2828d3de6151a76dad7b72781c +a10e4dcf51889f69e6bd4c052f8d4036b9571ced98a3d7d779cbcb9fa5c3a82228566ea7cc1d012bf56dea0a40c5a64c +a0ed026528d9a8bb3201bc9dcd20598933e8c72fd315deea8da63d06e97392aa729d98a55a8a60fa4d5573513ba5c9fe +b723fcd04cddbd4c36feae827a03746ffef251c4f4c55a88beedaeeee194430a99f566f483668a0d88b13e7a4a37f1de +84a2cdceed44828c7c05a6a762edec0165e434e7029df617d6646aba48776e6c3b823f40689cee136536f8c93e08a629 +b786264e3a237ac3a1d56c9f4e87438dfed620c867100fd38b01287f5b755c7820937403bfb86644e082094d3e410a00 +92cc35b2065fca157c7bba54410f8bd85907a01c9f760aa0ddb7a82cb55811d24cb4dc6b725367a6a1c293b809a48ead +a12bbf22b117f00164a42515bc57cc9e6c43cc77fb737ee3d0c0cad94cb50cd3847d61cab469cf8ca76f7958bdcfc771 +85985b00de533bde2a757eddf53be79ea39091d16af3fc92327bcd1cd59bf2bf4411a334da29ad775e8ffaf3cea7d7b8 +af9eb24185b0d330d0ea1d0b0fa78af0dcf42ced81cb0128f16cafdea687a9c5582bb6d7c5744117b271cd0b3303f0b5 +8c8aaa1d85ed6327f85d579767c7a9158d209171b3efcb3e8a9d9e534c078e821b6aade255101d2c9ef6d67ba66f10be +a450518a03ffb40e1df89e0f88fd55b5b06f4872cdfb7ec55f40dc40d9424b3b289866336c195bdd54597d95569e0096 +81e61cc69f93c435bd77f155e80626a9c764dd92b6c76af15c41346527948d8a6ca87d6351a0fe7987e2ee3aa66a9625 +b615e0cebf4fdff4cb23a20c8389c370915ba26aa703b28efe4ab070b1603d1c5b6541684acf46b52a915f6aee447539 +a7f51885c7a71885cc84ef734ecd107e8bf5f7a25131415f671d143cc1de92859e65001125323c7985799993af6c410d +abfbf7a46f32066989c32f774edcc68163f085ca81e94fe8c9fb32f8d451bbb2c20ac45cd8d97f9e618ab40186933b1a +8cf35a522b5cac1934004aa9dd236bc77198d43272888afa860cfc79b4b28dabf7a3c74098f84510897566fdd609aa45 +86aa927df78f7a06a4985eb0a4f0b93529cef14f9fd2812d46abffbf25e618ead14d99c70e3c3bb2e17f3f7fabc9c264 +860f1b4f4a398e9a8bb4739587cf96979cfbbe1687b7e91e5bd1198db726391b09b1a261bf12e96698818f60b5bd3537 +8e7c4ee19ff115881051e8637dce1f5d6c65e865d0c757e8ce41b6d7bcd86c7070cce60649692bbf28c868c7e2e1e2f4 +acf7ba01b0220419f09169ac8d16e5cc13dce08e88c90b8fdfaa33aab417f011a20b79a178d8a9f7211589d2e0affd7d +b404bde8e715aefbb9f20a353b911b79173ef3e2cf0aba98b5ae6190b90597d65043b0b4e014ad9ea6c77da2d213ea12 +97e3615d1c77a402253bb55da2d1cdf82de316cefffe42b1022c94b4818d6dc4a313731db85321c537914bdf716a875c +940e950b96a4096a578c6874d747515936652b9b113a5f27f5a834a610867b05f9881e2679b0b289b8527baa0009b6dd +8de15a13ca236a3a285ce6e6826c502ae7365bbe468b6e8ac67b15b0bb49be0e996f1eec81ef69e4b7f54f8e4779a054 +a12244777eacb08ecd42b5676b3a51153022ab97e9353ace0f47c6054c22de9ba60d2a60f59a36841c2a791cb1b7c288 +94f7580203e39a2642ee2e7c969b9911f011d7f3a90c398e1302d26edb3df03df1d0c43baa1c6cf90dde95296d49e742 +82ead33144aaecab965faf63af384565992f38fc1066e71e33d53f43ac93892e27fe78c4eaca1cccbc53364e26ff31e9 +a0c129e9706d354249a7f8aa664ccd7ede89aa1445c5547410814b56d10dc086720953363ab1da8ff5f1ed5d8e575104 +93b3057bf3f74edc95237781ae012cc4b1d3fd0455565ceaac7110290aa518ac32478ba4eb9851555fa87270fcc84f1f +949c2fd0b94f31f7cbf00c679bd3f6ec1a2f4056654708d39edf1a450b4e19a6e251d0bb24eb765087e698f61d3fca2c +99fd2e50e211ccb66b895eb2fc42f260f3ad5767f04c2fe238b81dae98aa6e3977443a51f4fe7b43f499caabe45699a5 +84fe19626503218f327b5325bfd7c0c3d2614b47d34964aa0259d564e769c6c81502132cc1765b0b31fbe39852706927 +b43287ec29d9010bec4284de58fed48dd1e129bac79f09d45153c9949131782f77b11b0c9f8ee06a39e5e9bbaa8e2c6d +908902f3ed45482df2f94415fc8e5a308057a40c8905d7cbbd58ec4848e19276577b7f7e69e5e684a8b981738e10f7ef +85cc7d9c1eae372b4f88758cd6e21604b4bc9f0794e1e74b6d9de96347f81944d01331385fae7a38e5f6096c1dc23465 +af60288c702082fc258b3dbd6952c6b75c1641a623905f491b1e72f49b9d39b33d150a336450abd3911a4c128166acdf +a7d8ac7e589558c4014369ab6f4c1f2196205b03e4278152ec0dbbd7ba54e803c3369a71d364a773aac8dbbd117e4a13 +9833aed34e48c206e9328073597aee1123f5bec085339b4e6839a389a429bf3042798a31fac1464ce963204adface76b +84631a4f012bbb62133030224b57deb32dcf464cacc8ffde7775adbe68707263ab5527a1c75e597e03aa703ba658b889 +a686a61f6467858a2a4c13e70ad81b1901290d3e51bbc0c6e366f9e652f575e91b11c75f640ccef8b0c6c1b05a43c9a0 +b585f0ffd5144907703b41539bfad7f9f058f5985f63db911064ba6b07af8da2796b84b16db42b8d11135c3f846cd9e2 +b525539516c7bb25f1d7e165f269dc8c9eedbba74df44887e178ab8fd798e2a31f39812ca922d6b64d91564f14012a64 +91e480d7568fd2fae39c35b0a8d623e66a3160fee1dd4e9097255004938b11ac1cd3918dc6a1e5fbcb700c95a547e5e8 +936ef55c69b842b6177de71fa48dc5442bf5132116b214302f8f242ca36a273a6bbfbfaf373777104dadbe8e7da5e970 +8e950c0f6688abdff8a3b8bd77be6da6f2565c7b55711f5860ea62a3ab1d51aac31821c602bc11a45e33c69e7dde3ea4 +90eed4595104a0527f8db1e028ff622ff70db4eae99cf47f6c2a0246ec7b103570a6a9a877e32e9647cc74969006743d +b756344f6c4ea05b792e416d9bd9ce9dd4bd904e7622761f28a85628506bfc9d88a25e5f04db62fad30a92fb1d8d8556 +ad79ba76534c1a02ac3e9b7308d390792984cd75b7e1d0e5e4ff123642d99d4ea1825643091aa8117336333c40d5bd94 +832b08144887de0c0341d84f6945450af8d7a4eb32367d7703118186c1be525df9382ce61fed5f3b65a0bb3449185f7f +a322fb944e46d8e47994820890c94af423674716da810ea1da71e0a7733ad72c22114ca39a4b59c98ce4291a5684c154 +b982851a65140dbea79bd3b5487e236feccee051deddcc17c2853032efca289ddb6eaf64be3dd85a73012fdbe9d2d4f3 +8eed5e230e201830b44b9fadca4e156fe1a16bf840cf29da0f381ea0587b20c226de2465c67e6268973e776809af68e1 +81c8f1c04490f36e41a53ee1b5185cb8adbb37c258fd6c3be8c56835bf574c37183a94d55b6554fca35d6e6dd9af0133 +8c4928724107cc16d36f2976677eac0b852fc4c3c0bb2f9cd4d59cd24a113faf33b2faf405c3fcce25be51d41e42c2c4 +8e4ba842636fdfc4d71f0983538ea5037d420acd26abd12efca48c252eea85544b2fa9fccdfec4e7c2a6359baffa112d +b4315b84700e26dec26f3488d308430fdff4809c10d4c24309627911cbb769ffaad0d1ecccd622dd02194eaf5ba59f91 +ab888308f757faef32648c1db01650dbc9aea248b09d06e6efcc996d395f48ec96f2d54a02de441d753fe8737862d991 +805094cfd77e207d5c75f3cad99f41f763ec15443052cfd758c6a82ba422d831a1103a7f9b100da49c28198279c3d3dc +ad857f33243e4a2cd2a773700def21fc7f94939d1a6d2c2125ecd58fc206ccafb07a2c02a1cfce19857d3654aca2c70c +a4d12d40149953daa70b89a329e918e9d93efb4e8004a9357fe76682dab9662c8507e16db83e849340f05cdb4933a373 +a0dbac2ed4b5d03606524245e8a31080eb5bd3e9a0c51dad88c3b18e3e6bc5d64953a81c8e60425b80107ee6b62b1fb4 +86da05355900f327164a78901f6e3db857531b33b1e855df1a67a9ba222c6b05fdb6b0ffbacaeb1ba5b45ff8979b6b68 +932c9873aa3e226dd922b5a616c75153bd0390ce8f332a414b9c8cb6606c2501a37a2aa88097bc7d8e2c4261706eb38c +accd9cdf07ccdd42033ce3b105e00bfd39e2304b1e3d66f8b1128645634452c20f759ec45adcef2fdf04408f62c4cc04 +b75cfdfc1cb48918752eab17eb579820ee6e71e6667abdb64df834ffc8c1362fbbc23ca2c80dee248fe1fbb72d87dfc8 +88b998c73b00638fde7d3dd650a08c5ab996dac6ac34251337fbff3fb5ae4a25dd20c1a16c987ad7ded19eca23cea891 +8afef0956c942571a27f504553fb312cca9e50ce41b44e0466d0516c5abe4d8acf4594cdb03b1ccdbe3f2e6a9093b713 +9042cd83c5ff261e9ebda26398caa16cac2cb840d19062fa8ae50e044c27104972948318f4c866dc4d578798272d3e49 +ad536719a64570a2cd1d72b6590ea1d02c8c49f259a7867be26c8191445165954bcfad50ea12688ace3fdfb0e98143bd +97c86328d63d297b6bc9718dc1ad5a05b908a750d1c455c700d84315589128ce4eea958aef2bcf0fcf4adbd8e3ce58d1 +8e592cf0802e6a9541eeb654dc55055e11f3d757847285197132935ca35bbb1a9156829a39384dfa6f645ff89eb36738 +ac16c614998944f77590bf3913a010e13f2d3bbf6a172293baf5983506c1a2d89989fb72e598f5bba1ea10a691377c93 +ab8e6f5b46baa6632de3621497bcbdd584decb999fe7d8a3364843a1e0b76497600630b6a24dd30119d8bcbfca29f335 +abe1d3af5279e60122d9cea8cc6581c819d7a0e20e3715da0f6da7e02d13a7653db643bd946e2fa9ba338eca81fbe140 +8c33bd831ecfb18d1d0713e16beba768e9c42df62170c1f8a16764912be77f2ac5915623d1d25e8c462aa9c2f6669ca4 +903692becae4a6409f7bdb127d9b11de57a5739fe24218dcbaa0092648d5332dfeef29a908ee9e43e5e0a51a4c3639bc +92591e90347ae286acd365eba32cd9ad8f20f4c9cad2dc579b195147ff290adf0d776bcb3d4b04a25d68a941fc0c781b +b64bbccf860299aec16e1f95c768a1f337c740bde612e6ba260e393edb8b04540127194761c42597abb9bcb771c576c3 +9194f056ccfdfeb78a11c5347e2255d7a7ebd1251f9aebc0b58feb68d3e03a7dbbb74e3ef7309455853adfb4694bd01a +aa4f15f6d6a53ae65b7f6f91e8981d07a5919d2138679a561f7bb608dc4596e45ca06c9441d51fb678b2ad89ae7a17ae +90e3d18507beb30bde08c5001faf489a19ab545c177efb3f73fbf5605f9a0abcdc8bfbc44f832d6028e3e0a834bea98f +8f31dc0118c8c88a6e79e502d10e57652b7aba8409a5bf572ca63fed6b7cbad7f28bbc92ac2264f649792fc1d0715085 +a307d1067ea4c56437b6f8913aa8fcbf4a24580fc1e3336e7f6518f0f3adb9c4733090e459a3f737414ec0048179c30a +b7cc41fdf89595cd81a821669be712cd75f3a6c7a18f95da7d7a73de4f51bb0b44771c1f7cd3cd949e6f711313308716 +a9dc74e197fe60e8c0db06b18f8fe536381946edecdf31e9bd90e1ebfcad7f361544884e2fe83c23b5632912ec284faf +8b3e1e81326d611567e26ed29108f33ddb838c45bbd1355b3ae7e5d463612af64b63fff9fa8e6f2c14c8806021a5a080 +92f6537bca12778866335acc1eb4c3dfc2c8e7e5cf03399743dcea46aa66cac92ac2963b0892784263ad0ebe26ffdbf6 +b5cc0061f7a3e41513199c7dd91ac60d727366482a4c7328527f7bd4fc3509412f711bb722b4413b3736a219b843d15d +b3e9711d68d2c6f6e2cc27e385d5f603d9a1c9a96edeefa1ffdf390439954d19504d6aadc566b47e229ad4940ef020d2 +a09d0d3f0e5dc73a4a0827b72710b514bbfce4a7fcd5141d498a5aad6c38071077f50d3f91af897d9ab677b7041dedda +b177fe260f3b86e9ac21f1bfbe2682ae5dd8c9aecebb84f37054bdab6e39094e611ce582210ceeddde66adf759dadb6d +b0ac6595eba9f5dc4b2fd21856267cfbcfb5b12aa34ec69ca32b80071c5b652e85c25a224d80443d503bf25fbbfe07e9 +81f3c0e11b196bd4a2e8f07f8c037002566dc9037da81f3988add458a520c24dd1be3d43d851e28c0c6a85de4b57a542 +a44308c95615f7fedb2d2127012924468c015df9f48359cc2e36ab4223870b0bfc1e9040baabefdf5266f93afaad896b +8493ec4c32d5a13b81039f1b436eb83f259945dc950e3c6c2ccf5087ec56dd2f60890ed4edf01728b6a54950e19b35c6 +a1a439ec2a6a95bdac9aaa925ff337ba956c0d236ab5318354270e73ed6b73b4ae2d27b4c1686cf97b6526d04e65be81 +b4659b7b53c55a4b2bbe210b53520b392f893500e18990d843b72d7379d45fb44dd1dd2184348d6fd853d6b9ecc6b7c6 +afb2c68d75d00130b0e1b4f250001920213121791698ec04262db714cf7b1408d39f6cc10421f954845aad5b8250b77e +b22b843b40a97210f94043b552f348f66743055a3f274856a738e7d90a625b80e9bbb80cbbb450e1666eb56b8bd5c60f +800895ced82fe13d5fff65a93b0051c3df698bf1221b682accfdb63e3970f669ca37025750697f4e8ff2a3322ad57be4 +b21f598c50d7b9f4a584d548f85e42055ef8e24991906d973749090261584c7f4f5e984b528926f7e75375dd84d51af8 +849b1c68192d18274598dd6d0bf48fb5ee3b1ba25b331cff2d06f345bef3bed49760ca5690848cf33388f6a9a32cd646 +aeb6fd9478b10ef456f6bbb1e6dd19b14475e65497772d12cfc097948383d3fbd191bf95f046b8bf1989954118e483d0 +b1b5e0ea2835f7fc8b66e7731e392b43d16cbce04b52906b6751ab1b91978899db5fecbdabc23a19dabb253005468136 +91b6b1284770cf6f7ef35bc0b872b76c7763ffcfa68f9c8cfabcb2f264a66d47598bb9293f6a40f4c3dd33c265f45176 +b9ffed029846487c2cfb8a4bb61782bd8a878f3afdb73c377a0ebe63139fa070e3fcdc583eec3a53fdc5a421ff1fa877 +998007249d041b0b40ff546131cfc86d0b3598dcedf9a8778a223f7ed68ba4833b97324cbb1de91292b8ff51beab44b3 +8eb77ce9e0e406bf6f002870fb2fd1447646dd240df9bd485f8e0869298a1fc799d8a41b130c04370e9a9cc5c7540ca5 +853db8157462c46f2af7e8f94f2ed1c9b9a7ba2896b4973296898ff3d523d6e29e0b63a5d26cecd5e490b33c87a4cecf +b1436b6f3278768f0979ee852944258f2599977d255bea6fc912ba17c5dff5bdc850cf3e1fc52be9d6d188e868670f4f +a76acbc5832019b3b35667ab027feff49f01199a80016620f5c463dfcbfb51bf276ed17b7b683158ba450660cc7973eb +94540cdb051faf3ae8b8c52662868c2dab66bd02505c4f5f8eb4d6b2e2e5fd9a610890c5dcf8fd887eee796d2b5753a8 +aa35099666bceccf4eb3b65b13bba88e30a8be93693ab6761d8e5523343e8d6dd42d977e66499352fe4e9e9784a1dd0d +894471aad17be54319083c4b5e40adcfacf7c36c4aab0b671030b7ef321c53590a25eccd836efd20f32a93185fd315bb +8f52a9f705bb0dea958fcfbd52e2b6c08ad0f89a07a6b2942c1b4c37eead0d97a38a9e9aeb08d5d59b7fa2a9347f738b +9031c16b4f936c9cab55585dc5064739f696c3347ee2c0792320c9f749e760d120e396e8485ffc79d81c9f3337ad3d1c +82090a0d0d9b05459ec1c328ecd4707c333b784e3aaa0ef0072cee1eac83f9a653a75d83b9f63512a8c41200494826b4 +92c3a9553001f9ea4d67236b8ad1a33275378202cc1babc03f313895458f4b2549bfbbbdd37bfb8fbff0decb6b9f820a +88651868f4da37338a22bc553388df5dd1dd0cb78c4d7d07c637d8f6faef4bed72476fdcd4304d5bedf3514011135f08 +83fa0141bfebd88063f1d787719721b4c6b19ecf565b866de9d7d5d1a890e0e3d859b364bb65f8f8e688654456a40263 +90a7fab753e5d56dfc0e53a6b4e6ab14508220f3a62b3f3f30570c4c9ad225e74122635826c92e8e3227ec45e551432a +8fa375b0345bf6e5e062d108f9feaec91029345ecac67ccf1264eac77b8654cbfdda1f10579f481889c0e210254eadde +b83f06116da9daebdb013b26724523f077debaf6bc618b48a7a68858a98d275f7899c4ec73a0a827219b9248dd81c8c9 +8be1cada55e0c5ebb4fd460b2d209ae5326285a20c8bdd54ed9d1a87302f4063c8730bfda52d9d40e0d6fe43a0628465 +a68ad6f813743ec13a811f2ef3982c82d9d9ac1f7733936aa1e122f8dc7f4a305cc221579ab8fc170c3f123a1576f9ab +8878f1128214fdbbb8a0edd85223741e021508ab6d36c50d38680f2951ee713ea056ed03f62b9461897963d50ceefe0b +acc0d43d1b0260528b7425b260a5dea445b232b37240759fc65fe26f7c9d8e51569c5722bc33e94de6492f4ba1783504 +ad80b1dd717b076910ee5ceabcb762e75e4d094dc83b93b65c16de1f75bc712cef223c05d5579c1561829406c07a97d9 +a6fc9803f9c09d95fc326cc284f42ea5566255eb215dba8a9afb0be155ea11bcc55938b2d16f01cd2f2eda218c715efb +83ad733dbdfbaae8095a403dbf09130513f4ed4f08dcf8dd76ce83d1ea72999b7eea3a7b731da0d2bc80a83c6ee0e3e0 +8748912fbd08cb34a85416b0937d9c4327e9eed20d6e30aeb024a7253f14f1e0d774f3326e54738d71aae080e28da0fe +8997e78d8acf23051428af67183ae9b2c4aa42b503745ffe33df35a35103c589987e1473ab14dcd28ee78ebcb10d8e95 +a2f340502a7eb3c4a36412e6f028321372c4fa18a4743945607424e932af1271fa3e6598a162c872072529576eba6283 +868ccf19b5044ab93b45c9ed3ae34fcb504fe1453d6c4a1d12c325032cf01eb90356de82080ed897e97dba13cae33a02 +ac8867005fe4354d67aa37b866a7e581d2f94f7bd0b9f4efb5c2d1370ec13147a60692051b02fd00ae60b512bce9b1ff +8fd01886b046819c83c12bb779e432b25ba13713f9227be702074ec3abb2bba6be37220a0a26a4bd4171b99b14e32bc4 +a128981ed199f92b5959975c150a93a62fec50b61c80a3fa0634d90fc8058f76f5cbee77aae6889af12d296b30e613cd +81fe618552ff7a36c9235c6d4066cf2f930b5b38de4089e18166e4a06ca5723eadd1976d25e34b74b3ce942300b23e5b +ab1223ea049e6e0fbf9b611de7fd7c15e5e9637cbd73aa0e36aea08a7503ba6804f2aa807186fdc9aa7f4f9195f72e24 +b97285286981b2665f898abc13f3243b63005bef8db4cab3f658bf6167036b61af400f08db0fc3c640a9c623b760690d +ae3ddff7c1f0fbb6a13dbbc667a61e863c2c7c51c2051e33cd61620142e7e30a7e0c4c1f8fbb512aa3a8640267c6ac26 +99c2a89d5bef236060e51c4f952664094c20fbfca647e5d24a55c1fb8df2f3df58244fbbf3635db07b1c29ee3234fa6f +a5010764d4b9cd3b410638334d1f70c5f4843f45b4f4a9316aaea5fbb2c510a97449dd7a07b49f47334a69d37d9955d3 +86706d011dcdc9e9d165d01fea1df68dd74bedaf15a39f92893c030cafe96f4498c4c1fec2d2136354341b3f440a1462 +88fd57eb62bd7dc35722f3a0576c2138403a2f663a2603482e8974a895cf56ddbb02657dc6b89eb2cf5c1f9d1aff6426 +b0dfd4c68e3acb6bb8a776adaa421fc5e268ed4d5964bb90a727091e5113b55b3f9c6d33cedb3ee47ff7acc5df8b1749 +93b92bc942e1a636fc5c2dc1840de5faf158a113d640d5a475b48e2c56ccccaf9db0e37e90ce74c4b3f5c9ac3b2eb523 +b29a16fa1ea95cbfc1873c435ad40dc8495ba6341801b72bd95d908147dcffb1b4bb426dd635f3af4c88984f56594dd8 +b8f367105e1a2d554ac30200c66aeb579d3d30a8953d20fb6ebba2d876ec39c52ea5d654f1bb89b8ddf3d9d651f31cdf +b5fbc228c983d08adf8612eba5b3db3acff604439226f86aa133b02cce4ffde2f977c8dbb8b446b4375673f71634c89d +a399bea37d3056e0559f6644faa0af93063b4b545d504d7e228d3dbbc294af83d3c4cf37fe026b63899b4e7d50fd08f5 +928ef411a36414b24aea26fdbed4bdb1bb6bdc2d967e2553ce54c7c4e077e76869cea590257645c9129dd55ce025295c +9684a4adeed416a9ce82ad79b55c4a3adcfbd43950bc442ed8a340381caedb70f4baaaf821e3a152f483f965d8f56162 +92558a37f214d6f4cb6d72cd2f4ad24dff9d17611b9e4a41ee5c741a5d1ca9e4053b0584533ef4da206110b5dc3e2a35 +973bf0724d1785cc5e85d2a8ee8c354ad4cf557217ced0b7940f6f064024c20b2bfc5b144c820b5083da4bf70690de4d +adaf1389dfa528210ca9c2657c5ff10d51f7e3b18e93a59c37211be0506c3576cb2c04ec80cd0f82605e53c5a3556620 +85b58b223b09fda6f3ab674d75e780c49eb2167837243df049281e8f4fed653811138b398db9cdfe7405fdb8485602fe +849504d3db408d80745a07e850b0a804607b91a59922a5d3bc40da2748c029c029419cda38d2a4485cc0824c6b2504f0 +a3f4afcb353bc2582a02be758ebf0cd18752410ca2e64231176bfa23828423e0a450a65f241a9ed8eab36cae8d9c567b +ae362786cdf121206537af9590d330abbc6dc328b53cdd145dbed0e5df1364c816aae757c4c81f9d619e3698dd32bcdf +9024cfa5b0101eb02ab97866d5a3832944e5aa6888484cfba3d856576b920787b364fba5956bd7c68a305afedc958201 +8a116df09fed923acefb2aecf38a4fbc4b973ee964d67f03791d70bee6356af43ffca117d4e9463ffaf0e0d5d5e5a69f +9163016175c73f1bbc912ddfe03bd4e1db19c64951c8909ee6befe71a1249d838e0db49f03670bb4c5c9b2ab0fb4fef3 +8f6357318d8d16e7240a02b05ce5a4976b6079d49daa258789c6dbf4a47950ebe9de6411780fab06c7c1f35651433380 +8e63cbae8be7341892dbedee3111adf0307c4ee9e375181aa53478f5ba9cdce164d6ae890e5f480119a3a51c6e989165 +a9782f30674a4874d91bfba7eda63aeb5dbe66b040c768d6a925d8ee135f0655ea56276b105239cc0668fc91ddb68cd1 +8d9d94b61ab84ec08665cbe0244ea41756785df019e453ef078c19380bd44c39d2958e8465c72eacf41eed5696037805 +b1470e6f5d2e314474937cb5a3bc30c8bf5fc3f79014945f6ee895fe20028ffc272f9d3a7320aac93e36c96d8a5454e3 +a444911bbafc71179766594f3606b6eaff041826607fd3192f62dec05cd0f01b78598609a530f6930e8440db66f76713 +a9823d44e2638fca7bcc8796cc91c3eb17f46ad6db9f7f6510e093727614aa3a4f9b2c4011ef91dc1c2d224d08d8d05b +ab86020972c359ab98294212558b4b14862040139876c67fc494184b5c9bcea1dbe32fe0c8dd9e60be9daa304acd599a +b7e5cb685bbdcfdb1e48259a5d68d047846c8a35c5b3f90172fb183d1df40d22eaf0edaca2761a07c29c577000ccfed0 +8c88319dae4b28989817e79e6667fd891181e8d2ed91b9c6b614985bca14b12982462ec58b17be0463c24bbb79dd62a1 +8c1c6867e7107fb2178157c991b9c8b0f90c8d57a51220bf3650438ccabccf62da4db8a9916491e730ff3d0c106496e3 +a00a79bd58da6528b9af033087260f9f3d00519eafb4746b355204ee994e89481591b508eaa5402821083e250d38467b +8785abd7c37690f6aa870ee5c799eef72e398a7898b6767f698515be277b9c2fc1af12ea89b0620a848221343a3b5ec3 +8aadae68543db65cef71d0e230a09508d72061398ef2fabec0f856aacff2125b79c70e620744aaf331faf3dfc8afb9bc +8ff0cd437fcad9630b8a2333176a55e178db4142ec841581590594d74d5b53baeac5fb903fdf7bcf83e245b95b58285e +af274e8fad6b190be4e5dc92d2705ba6ac0d7e1ea29e958a5cdd4cb764de46a56d9eef62c999a16e7c50a50b2d9fe3a8 +865e6ec7d1aa848786d6a7a4e87a24d442311f0810b01ef5a74928ab59fdfd651e48880b49680047e5b0df6b3c7c2ecc +800706baaeb35bf3bc33bdea9a8b5cb00d82df407b3b7e1b781a9359cf44fb410ed311591080181b768aae223d9246aa +a9496389d0780b309c6998374ae159f58a8d0fe9a1c24c36cebcb45b27d818e653b51a8ee1f01e30a9b2c46a548126ef +b5fccf4fc3186661939fbee2e89c2aa0e3a6ad4907bcc98c7750520540c4c183b1bbfcdf47f2f1c5e75c3a30cdf30c75 +a90028e39081b736e628c2230cc1338f9210ed01309a40fdf08d39c10cced2cdf71271013bea6dba3a0444fe47963106 +a0815cbb325a8fecf2e1bcc5046644be32d43a8001bd5d8cf0022e4572cd0d481b3e717002f7ab21e16da5f5d16886d6 +b2024787fcda52abc4138150f15e81f4a5be442929b1651ddccbfd558029912be4d61c3c9b467605fff640edf7392494 +ab5aa60032304a584cc9245a33f528eae7157808dedd1ad83ebae00aadc25dbe1cd5917eb8b6b2c800df15e67bdd4c4d +866643847ef512c5119f2f6e4e3b8d3f4abb885f530bb16fcef0edb698a5b0768905e51536283925b6795a5e68b60ddc +806aa99c9a46ee11cc3ebf0db2344b7515db8c45b09a46a85f8b2082940a6f7263f3c9b12214116c88310e706f8e973a +a6eada8b9ff3cd010f3174f3d894eb8bb19efdbff4c6d88976514a5b9968b0f1827d8ac4fe510fb0ba92b64583734a1e +98480db817c3abbc8b7baedf9bf5674ec4afcfd0cd0fd670363510a426dad1bcf1b1cb3bf0f1860e54530deb99460291 +81ab480187af4a3dfbc87be29eca39b342a7e8e1d1df3fc61985e0e43d8d116b8eac2f1021bde4ae4e5e3606c1b67a21 +8a37df12dc997bf9b800f8fd581a614a1d5e32b843f067d63d1ca7fde2e229d24413d3a8308ec1e8389bf88154adb517 +b045a55ca0bb505bd5e8fcc4cfdd5e9af1a7d5fe7a797c7ede3f0b09712b37f493d3fcf6ef0e759d7e0157db1f583c95 +ad502e53a50691238323642e1d8b519b3c2c2f0fd6a0dd29de231f453be730cf1adc672887d97df42af0a300f7631087 +80597648f10c6d8fcd7421caf4e7f126179633078a1724817d2adc41b783723f302eabc947a7ba7767166dacf4ce8fa1 +aefb56427966c81081999dffbe89f8a0c402041929cd4e83d6612866cfbb97744f4ab802578349fbecc641fa9955e81b +a340e493fb3fb604eab864d4b18a6e40ba657003f1f88787e88e48b995da3d0ab4926ce438bdc8d100a41912a47dace0 +a6d777bfc0895eac541a092e14499ff8bf7156689d916a678b50a1460583b38e68158984bea113a0a8e970d8a6799a85 +90ce469410f0e8cfff40472817eb445770833cdcf2895a69bc32bcf959854d41712599ceb2b0422008d7300b05e62e02 +815c51be91d8516d5adc2fd61b6600957ed07cf5fdc809aa652b059bea8ed179638a19077a3f040334032f0e7900ac8b +b3ec6c0c3c007c49c6b7f7fc2ffd3d3a41cdff5ad3ac40831f53bfc0c799ffeed5f440a27acc5f64432e847cc17dd82e +823637abeab5fb19e4810b045254558d98828126e9a2d5895a34b9e4b4f49ab0a5b3ee2422f1f378995ea05df5516057 +ac05412bcf46c254f6548d8107a63928bba19ab6889de5d331eb68cf4d8ce206055b83af4cb7c6c23b50188391e93f84 +88514163c587068178302bc56e9a8b3ad2fa62afd405db92f2478bb730101358c99c0fe40020eeed818c4e251007de9c +b1e657d0f7772795b3f5a84317b889e8ded7a08ea5beb2ab437bebf56bcb508ae7215742819ed1e4ae3969995fe3b35d +a727d4f03027fe858656ca5c51240a65924915bd8bd7ffa3cfc8314a03594738234df717e78bb55a7add61a0a4501836 +b601682830fc4d48ece2bdc9f1a1d5b9a2879c40c46135f00c2c3ae1187c821412f0f0cfbc83d4e144ddd7b702ca8e78 +b5cfea436aa1f29c4446979272a8637cb277f282825674ddb3acac2c280662fb119e6b2bdd52c4b8dbf2c39b1d2070d6 +85c211645ff746669f60aa314093703b9045966604c6aa75aae28422621b256c0c2be835b87e87a00d3f144e8ab7b5f0 +867628d25bab4cb85d448fd50fdd117be1decdd57292e194a8baa0655978fae551912851660a1d5b9de7a2afbb88ef5c +a4e79c55d1b13c959ff93ddcf1747722c6312a7941a3b49f79006b3165334bab369e5469f1bddebadb12bfaff53806d5 +ac61f0973e84546487c5da7991209526c380e3731925b93228d93a93bce1283a3e0807152354f5fe7f3ea44fc447f8fe +a1aa676735a73a671a4e10de2078fd2725660052aa344ca2eb4d56ee0fd04552fe9873ee14a85b09c55708443182183a +8e2f13269f0a264ef2b772d24425bef5b9aa7ea5bbfbefbcc5fd2a5efd4927641c3d2374d0548439a9f6302d7e4ba149 +b0aacdaf27548d4f9de6e1ec3ad80e196761e3fb07c440909524a83880d78c93465aea13040e99de0e60340e5a5503cd +a41b25ae64f66de4726013538411d0ac10fdb974420352f2adb6ce2dcad7b762fd7982c8062a9bac85cdfcc4b577fd18 +b32d87d5d551f93a16ec983fd4ef9c0efcdae4f5e242ce558e77bcde8e472a0df666875af0aeec1a7c10daebebab76ea +b8515795775856e25899e487bf4e5c2b49e04b7fbe40cb3b5c25378bcccde11971da280e8b7ba44d72b8436e2066e20f +91769a608c9a32f39ca9d14d5451e10071de2fd6b0baec9a541c8fad22da75ed4946e7f8b081f79cc2a67bd2452066a9 +87b1e6dbca2b9dbc8ce67fd2f54ffe96dfcce9609210a674a4cb47dd71a8d95a5a24191d87ba4effa4a84d7db51f9ba0 +a95accf3dbcbf3798bab280cabe46e3e3688c5db29944dbe8f9bd8559d70352b0cfac023852adc67c73ce203cbb00a81 +a835f8ce7a8aa772c3d7cfe35971c33fc36aa3333b8fae5225787533a1e4839a36c84c0949410bb6aace6d4085588b1e +8ef7faa2cf93889e7a291713ab39b3a20875576a34a8072a133fed01046f8093ace6b858463e1e8a7f923d57e4e1bc38 +969ecd85643a16d937f148e15fb56c9550aefd68a638425de5058333e8c0f94b1df338eaab1bd683190bfde68460622b +8982f4c76b782b9b47a9c5aeb135278e5c991b1558e47b79328c4fae4b30b2b20c01204ff1afb62b7797879d9dee48e2 +b5098b7ba813178ced68f873c8c223e23a3283d9f1a061c95b68f37310bca4b2934a3a725fff1de1341c79bb3ba6007e +97b160787009f7b9649ed63db9387d48a669e17b2aba8656792eb4f5685bb8e6386f275476b4dfbb1b4cb0c2a69bc752 +88b69369c71daad6b84fa51a0f64a6962d8c77e555b13c035ad6fa1038e7190af455b1bd61ae328b65d6a14cf3d5f0d5 +af88b87801361f0de26bd2533554ee6f4d8067e3122b54161c313c52cc9eafea00661c5c43e2d533485d1f26da4e5510 +98ab18e3bbcb23ac1e34439849e56009bb765ab2f2558ebfd0a57cbe742169f114bceb930533fb911b22cb5a8fe172bc +9027507f1725d81e5ac0f0854c89ab627df3020fe928cb8745f887bf3310086c58fca1119fd5cd18a7d3561c042d58de +a676583f8a26e6f8991a0791916ce785b596ce372812f5eb7b4243ba9367ea95c797170fdac5b0c5e6b7f6519cc2b026 +b91b0ab32638aef3365035a41c6068e36d2303bfee8640565e16c9a56c21703270fd45946ce663238a72c053eb3f2230 +aaf4cd1ac0a30906dcd2b66b37848c6cc443da511e0b0367fd792887fdaf1500551590440e61d837dbee9d24c9801108 +a06f20a02d3cd76029baad5a12592f181738378a83a95e90470fa7cc82a5ae9d2ed824a20eeb1e96e6edc0619f298688 +a465d379c3481b294efc3f2f940b651c45579607cf72d143b99705eae42103a0279eb3595966453130e18935265e35d6 +892a8af7816a806295278027a956663ea1297118ede0f2a7e670483b81fb14dccacc7a652e12f160e531d806ca5f2861 +b480917c0e8b6e00de11b4416a20af6c48a343450a32ee43224559d30e1fecdece52cc699493e1754c0571b84f6c02c2 +b3182da84c81e5a52e22cebed985b0efc3056350ec59e8646e7fd984cdb32e6ac14e76609d0ffaca204a7a3c20e9f95d +a04ea6392f3b5a176fa797ddec3214946962b84a8f729ffbd01ca65767ff6237da8147fc9dc7dd88662ad0faefdb538c +95c0d10a9ba2b0eb1fd7aa60c743b6cf333bb7f3d7adedce055d6cd35b755d326bf9102afabb1634f209d8dacfd47f1a +a1a583d28b07601541fa666767f4f45c954431f8f3cc3f96380364c5044ff9f64114160e5002fb2bbc20812b8cbd36cb +a1a0708af5034545e8fcc771f41e14dff421eed08b4606f6d051f2d7799efd00d3a59a1b9a811fa4eddf5682e63102ea +ab27c7f54096483dd85c866cfb347166abe179dc5ffaca0c29cf3bfe5166864c7fa5f954c919b3ba00bdbab38e03407d +ac8c82271c8ca71125b380ed6c61b326c1cfe5664ccd7f52820e11f2bea334b6f60b1cf1d31599ed94d8218aa6fbf546 +a015ea84237d6aa2adb677ce1ff8a137ef48b460afaca20ae826a53d7e731320ebdd9ee836de7d812178bec010dd6799 +925418cda78a56c5b15d0f2dc66f720bda2885f15ffafb02ce9c9eed7167e68c04ad6ae5aa09c8c1c2f387aa39ad6d1b +87c00bba80a965b3742deacafb269ca94ead4eb57fdb3ed28e776b1d0989e1b1dba289019cfb1a0f849e58668a4f1552 +948d492db131ca194f4e6f9ae1ea6ebc46ebbed5d11f1f305d3d90d6b4995b1218b9606d114f48282a15661a8a8051ca +8179617d64306417d6865add8b7be8452f1759721f97d737ef8a3c90da6551034049af781b6686b2ea99f87d376bce64 +918e3da425b7c41e195ed7b726fa26b15a64299fe12a3c22f51a2a257e847611ac6cfcc99294317523fc491e1cbe60c4 +a339682a37844d15ca37f753599d0a71eedfbbf7b241f231dd93e5d349c6f7130e0d0b97e6abd2d894f8b701da37cb11 +8fc284f37bee79067f473bc8b6de4258930a21c28ac54aaf00b36f5ac28230474250f3aa6a703b6057f7fb79a203c2c1 +a2c474e3a52a48cd1928e755f610fefa52d557eb67974d02287dbb935c4b9aab7227a325424fed65f8f6d556d8a46812 +99b88390fa856aa1b8e615a53f19c83e083f9b50705d8a15922e7c3e8216f808a4cc80744ca12506b1661d31d8d962e4 +a1cbd03e4d4f58fc4d48fa165d824b77838c224765f35d976d3107d44a6cf41e13f661f0e86f87589292721f4de703fb +b3a5dde8a40e55d8d5532beaa5f734ee8e91eafad3696df92399ae10793a8a10319b6dc53495edcc9b5cfd50a389a086 +996e25e1df5c2203647b9a1744bd1b1811857f742aee0801508457a3575666fcc8fc0c047c2b4341d4b507008cd674c2 +93e0a66039e74e324ee6c38809b3608507c492ef752202fff0b2c0e1261ca28f1790b3af4fdb236f0ed7e963e05c1ec0 +b6084e5818d2d860ac1606d3858329fbad4708f79d51a6f072dc370a21fdb1e1b207b74bc265a8547658bfb6a9569bb3 +a5336126a99c0ecfc890584b2a167922a26cae652dfc96a96ab2faf0bf9842f166b39ceaf396cd3d300d0ebb2e6e0ebf +b8b6f13ce9201decaba76d4eca9b9fa2e7445f9bc7dc9f82c262f49b15a40d45d5335819b71ff2ee40465da47d015c47 +b45df257b40c68b7916b768092e91c72b37d3ed2a44b09bf23102a4f33348849026cb3f9fbb484adfea149e2d2a180ff +a50d38ee017e28021229c4bb7d83dd9cdad27ab3aa38980b2423b96aa3f7dc618e3b23895b0e1379ca20299ff1919bbf +97542cf600d34e4fdc07d074e8054e950708284ed99c96c7f15496937242365c66e323b0e09c49c9c38113096640a1b6 +822d198629697dcd663be9c95ff1b39419eae2463fa7e6d996b2c009d746bedc8333be241850153d16c5276749c10b20 +9217bc14974766ebdfbf6b434dd84b32b04658c8d8d3c31b5ff04199795d1cfad583782fd0c7438df865b81b2f116f9c +93477879fa28a89471a2c65ef6e253f30911da44260833dd51030b7a2130a923770ebd60b9120f551ab373f7d9ed80aa +87d89ff7373f795a3a798f03e58a0f0f0e7deab8db2802863fab84a7be64ae4dcf82ece18c4ddbefccd356262c2e8176 +a3ba26bd31d3cc53ceeced422eb9a63c0383cde9476b5f1902b7fe2b19e0bbf420a2172ac5c8c24f1f5c466eecc615d4 +a0fe061c76c90d84bd4353e52e1ef4b0561919769dbabe1679b08ef6c98dcfb6258f122bb440993d976c0ab38854386b +b3070aa470185cb574b3af6c94b4069068b89bb9f7ea7db0a668df0b5e6aabdfe784581f13f0cf35cd4c67726f139a8c +9365e4cdf25e116cbc4a55de89d609bba0eaf0df2a078e624765509f8f5a862e5da41b81883df086a0e5005ce1576223 +a9036081945e3072fa3b5f022df698a8f78e62ab1e9559c88f9c54e00bc091a547467d5e2c7cbf6bc7396acb96dd2c46 +8309890959fcc2a4b3d7232f9062ee51ece20c7e631a00ec151d6b4d5dfccf14c805ce5f9aa569d74fb13ae25f9a6bbe +b1dc43f07303634157f78e213c2fae99435661cc56a24be536ccbd345ef666798b3ac53c438209b47eb62b91d6fea90a +84eb451e0a74ef14a2c2266ff01bd33d9a91163c71f89d0a9c0b8edfcfe918fc549565509cd96eed5720a438ff55f7f2 +9863b85a10db32c4317b19cc9245492b9389b318cf128d9bbc7ec80a694fcbbd3c0d3189a8cad00cc9290e67e5b361ee +8a150ee474ebe48bdfcac1b29e46ac90dcded8abbe4807a165214e66f780f424be367df5ef1e94b09acf4a00cd2e614d +a6677a373130b83e30849af12475e192f817ba4f3226529a9cca8baaefb8811db376e4a044b42bf1481268c249b1a66e +b969cbf444c1297aa50d1dfa0894de4565161cb1fc59ba03af9655c5bf94775006fe8659d3445b546538a22a43be6b93 +8383167e5275e0707e391645dc9dea9e8a19640ecfa23387f7f6fcaddff5cde0b4090dfad7af3c36f8d5c7705568e8d8 +a353ddbc6b6837773e49bb1e33a3e00ca2fb5f7e1dba3a004b0de75f94a4e90860d082a455968851ef050ae5904452e0 +adeccf320d7d2831b495479b4db4aa0e25c5f3574f65a978c112e9981b2663f59de4c2fa88974fdcabb2eedb7adab452 +afa0eacc9fdbe27fb5e640ecad7ecc785df0daf00fc1325af716af61786719dd7f2d9e085a71d8dc059e54fd68a41f24 +a5b803a5bbe0ca77c8b95e1e7bacfd22feae9f053270a191b4fd9bca850ef21a2d4bd9bcd50ecfb971bb458ff2354840 +b023c9c95613d9692a301ef33176b655ba11769a364b787f02b42ceb72338642655ea7a3a55a3eec6e1e3b652c3a179e +8fa616aa7196fc2402f23a19e54620d4cf4cf48e1adfb7ea1f3711c69705481ddcc4c97236d47a92e974984d124589e5 +a49e11e30cb81cb7617935e8a30110b8d241b67df2d603e5acc66af53702cf1e9c3ef4a9b777be49a9f0f576c65dcc30 +8df70b0f19381752fe327c81cce15192389e695586050f26344f56e451df2be0b1cdf7ec0cba7ce5b911dcff2b9325ae +8fbbc21a59d5f5a14ff455ca78a9a393cab91deb61cf1c25117db2714d752e0054ed3e7e13dd36ad423815344140f443 +a9a03285488668ab97836a713c6e608986c571d6a6c21e1adbd99ae4009b3dde43721a705d751f1bd4ebf1ea7511dfed +b2f32b8e19e296e8402251df67bae6066aeefd89047586d887ffa2eacdf38e83d4f9dc32e553799024c7a41818945755 +942cf596b2278ad478be5c0ab6a2ad0ceafe110263cc93d15b9a3f420932104e462cf37586c374f10b1040cb83b862e0 +aaa077a55f501c875ceae0a27ef2b180be9de660ef3d6b2132eb17256771ce609d9bc8aaf687f2b56ae46af34ad12b30 +90ac74885be1448101cf3b957d4486e379673328a006ea42715c39916e9334ea77117ff4a60d858e2ccce9694547a14f +9256cdfc2339e89db56fd04bd9b0611be0eefc5ee30711bcece4aadf2efcc5a6dcc0cfd5f733e0e307e3a58055dff612 +a4c7384e208a0863f4c056248f595473dcde70f019ddaede45b8caf0752575c241bac6e436439f380ac88eee23a858e9 +a3aa67391781e0736dddc389f86b430b2fc293b7bd56bfd5a8ec01d1dd52ed940593c3ad4ce25905061936da062b0af6 +80299275ec322fbb66cc7dce4482ddd846534e92121186b6906c9a5d5834346b7de75909b22b98d73120caec964e7012 +aa3a6cd88e5f98a12738b6688f54478815e26778357bcc2bc9f2648db408d6076ef73cced92a0a6b8b486453c9379f18 +b07c444681dc87b08a7d7c86708b82e82f8f2dbd4001986027b82cfbed17b9043e1104ade612e8e7993a00a4f8128c93 +af40e01b68d908ac2a55dca9b07bb46378c969839c6c822d298a01bc91540ea7a0c07720a098be9a3cfe9c27918e80e8 +abd8947c3bbc3883c80d8c873f8e2dc9b878cbbb4fc4a753a68f5027de6d8c26aa8fbbafeb85519ac94e2db660f31f26 +a234f9d1a8f0cb5d017ccca30b591c95ec416c1cb906bd3e71b13627f27960f61f41ed603ffbcf043fd79974ec3169a8 +835aaf52a6af2bc7da4cf1586c1a27c72ad9de03c88922ad172dce7550d70f6f3efcc3820d38cd56ae3f7fc2f901f7a0 +ae75db982a45ad01f4aa7bc50d642ff188219652bb8d521d13a9877049425d57852f3c9e4d340ffec12a4d0c639e7062 +b88884aa9187c33dc784a96832c86a44d24e9ffe6315544d47fc25428f11337b9ffd56eb0a03ad709d1bf86175059096 +8492ca5afcc6c0187b06453f01ed45fd57eb56facbeea30c93686b9e1dab8eaabd89e0ccb24b5f35d3d19cd7a58b5338 +9350623b6e1592b7ea31b1349724114512c3cce1e5459cd5bddd3d0a9b2accc64ab2bf67a71382d81190c3ab7466ba08 +98e8bf9bed6ae33b7c7e0e49fc43de135bffdba12b5dcb9ff38cb2d2a5368bb570fe7ee8e7fbe68220084d1d3505d5be +ab56144393f55f4c6f80c67e0ab68f445568d68b5aa0118c0c666664a43ba6307ee6508ba0bb5eb17664817bc9749af0 +827d5717a41b8592cfd1b796a30d6b2c3ca2cdc92455f9f4294b051c4c97b7ad6373f692ddafda67884102e6c2a16113 +8445ce2bb81598067edaa2a9e356eda42fb6dc5dd936ccf3d1ff847139e6020310d43d0fec1fe70296e8f9e41a40eb20 +9405178d965ee51e8d76d29101933837a85710961bb61f743d563ef17263f3c2e161d57e133afac209cdb5c46b105e31 +b209f9ed324c0daa68f79800c0a1338bbaf6d37b539871cb7570f2c235caca238a2c4407961fcb7471a103545495ef2c +92ae6437af6bbd97e729b82f5b0d8fb081ca822f340e20fae1875bdc65694cd9b8c037a5a1d49aa9cae3d33f5bad414e +9445bdb666eae03449a38e00851629e29a7415c8274e93343dc0020f439a5df0009cd3c4f5b9ce5c0f79aefa53ceac99 +93fdab5f9f792eada28f75e9ac6042a2c7f3142ba416bfdb1f90aa8461dbe4af524eee6db4f421cb70c7bc204684d043 +a7f4dc949af4c3163953320898104a2b17161f7be5a5615da684f881633174fb0b712d0b7584b76302e811f3fac3c12f +a8ac84da817b3066ba9789bf2a566ccf84ab0a374210b8a215a9dcf493656a3fa0ecf07c4178920245fee0e46de7c3ec +8e6a0ae1273acda3aa50d07d293d580414110a63bc3fb6330bb2ee6f824aff0d8f42b7375a1a5ba85c05bfbe9da88cb5 +a5dea98852bd6f51a84fa06e331ea73a08d9d220cda437f694ad9ad02cf10657882242e20bdf21acbbaa545047da4ce5 +b13f410bf4cfce0827a5dfd1d6b5d8eabc60203b26f4c88238b8000f5b3aaf03242cdeadc2973b33109751da367069e1 +a334315a9d61b692ad919b616df0aa75a9f73e4ea6fc27d216f48964e7daebd84b796418580cf97d4f08d4a4b51037cd +8901ba9e963fcd2f7e08179b6d19c7a3b8193b78ca0e5cf0175916de873ca0d000cd7ac678c0473be371e0ac132f35a2 +b11a445433745f6cb14c9a65314bbf78b852f7b00786501b05d66092b871111cd7bee25f702d9e550d7dd91601620abb +8c2f7b8e7b906c71f2f154cc9f053e8394509c37c07b9d4f21b4495e80484fc5fc8ab4bdc525bd6cfa9518680ba0d1a2 +b9733cebe92b43b899d3d1bfbf4b71d12f40d1853b2c98e36e635fdd8a0603ab03119890a67127e6bc79afae35b0bef2 +a560f6692e88510d9ba940371e1ada344caf0c36440f492a3067ba38e9b7011caac37ba096a8a4accb1c8656d3c019b3 +ac18624339c1487b2626eef00d66b302bdb1526b6340d6847befe2fdfb2b410be5555f82939f8707f756db0e021ed398 +afd9a3b8866a7fe4f7bc13470c0169b9705fcd3073685f5a6dcff3bdbbc2be50ac6d9908f9a10c5104b0bffc2bc14dad +97f15c92fe1f10949ed9def5dd238bc1429706e5037a0e0afb71c2d0e5845e2fed95a171c393e372077a7c7059f8c0e0 +9453a1d4d09c309b70968ea527007d34df9c4cfd3048e5391aac5f9b64ca0c05dde5b8c949c481cfc83ef2e57b687595 +b80e4b7c379ad435c91b20b3706253b763cbc980db78f782f955d2516af44c07bbfa5888cbf3a8439dc3907320feb25a +8939f458d28fefe45320b95d75b006e98330254056d063e4a2f20f04bcb25936024efe8d436d491ed34b482f9b9ae49c +a9ead2e833f71f7e574c766440c4b3c9c3363698c7ade14499a56003a272832ee6d99440887fa43ccdf80265b9d56b97 +b6547a36934f05ce7b779e68049d61351cf229ae72dc211cc96a2a471b2724782f9355fdb415ea6f0ea1eb84fe00e785 +828bfb3099b7b650b29b0f21279f829391f64520a6ab916d1056f647088f1e50fac9253ef7464eceab5380035c5a59c4 +8d714b9ea650be4342ff06c0256189e85c5c125adf6c7aeca3dba9b21d5e01a28b688fc2116ce285a0714a8f1425c0b8 +8a82eda041b2e72a3d73d70d85a568e035fbd6dc32559b6c6cfdf6f4edcb59a6ba85b6294a721aa0a71b07714e0b99ae +af5665ebc83d027173b14ffb0e05af0a192b719177889fadc9ac8c082fda721e9a75d9ce3f5602dbfd516600ee3b6405 +a68fdddf03d77bebdb676e40d93e59bd854408793df2935d0a5600601f7691b879981a398d02658c2da39dbbf61ef96c +8c001ebc84fcf0470b837a08a7b6125126b73a2762db47bbdc38c0e7992b1c66bac7a64faa1bf1020d1c63b40adc3082 +8553889b49f9491109792db0a69347880a9cf2911b4f16f59f7f424e5e6b553687d51282e8f95be6a543635247e2e2c2 +a2c269d6370b541daf1f23cc6b5d2b03a5fa0c7538d53ae500ef875952fe215e74a5010329ff41461f4c58b32ad97b3d +a5dae097285392b4eba83a9fd24baa03d42d0a157a37fae4b6efc3f45be86024b1182e4a6b6eadcf5efe37704c0a1ae5 +89871a77d2032387d19369933cd50a26bda643e40cfd0ce73febe717a51b39fae981406fd41e50f4a837c02a99524ef9 +8a76d495e90093ec2ac22f53759dc1cf36fbb8370fb586acbd3895c56a90bbf3796bcc4fc422ca4058adf337ead1402e +ad4eb7576c4954d20623c1336c63662c2a6fb46ec6ef99b7f8e946aa47488dcb136eab60b35600f98c78c16c10c99013 +894c2b120cec539feb1d281baaadde1e44beafedeeec29b804473fe024e25c1db652f151c956e88d9081fb39d27e0b19 +9196bd5c100878792444c573d02b380a69e1b4b30cb59a48114852085058a5fd952df4afee3ecceb5c4ede21e1ed4a1a +a996fffc910764ea87a1eedc3a3d600e6e0ff70e6a999cb435c9b713a89600fc130d1850174efe9fc18244bb7c6c5936 +8591bb8826befa8bee9663230d9a864a5068589f059e37b450e8c85e15ce9a1992f0ce1ead1d9829b452997727edcf9d +9465e20bb22c41bf1fa728be8e069e25cda3f7c243381ca9973cbedad0c7b07d3dd3e85719d77cf80b1058ce60e16d68 +926b5ce39b6e60b94878ffeae9ff20178656c375fb9cfe160b82318ca500eb3e2e3144608b6c3f8d6c856b8fe1e2fbcf +a1ef29cbc83c45eb28ad468d0ce5d0fdd6b9d8191ba5ffa1a781c2b232ed23db6b7b04de06ef31763a6bfe377fa2f408 +9328e63a3c8acf457c9f1f28b32d90d0eeadb0f650b5d43486a61d7374757a7ada5fc1def2a1e600fa255d8b3f48036f +a9c64880fcb7654f4dd08f4c90baac95712dd6dd407e17ea60606e9a97dc8e54dd25cb72a9bf3fc61f8d0ad569fe369d +a908eb7b940c1963f73046d6b35d40e09013bfbfbeb2ccd64df441867e202b0f3b625fa32dd04987c3d7851360abdffc +b3947b5ed6d59e59e4472cdb1c3261de1b5278fb7cb9b5fca553f328b3b3e094596861ea526eca02395f7b7358155b7b +99da7f190d37bc58945f981cf484d40fcf0855cf8178e2ce8d057c7f0a9d9f77425fdbce9ef8366f44f671b20fd27d0b +913976d77d80e3657977df39571577fdf0be68ba846883705b454f8493578baa741cfaede53783e2c97cc08964395d83 +8d754a61e5164a80b5090c13f3e936056812d4ae8dc5cc649e6c7f37464777249bc4ae760a9806939131f39d92cca5bf +82ffd098480828a90cb221a8c28584e15904bad477c13b2e2d6ef0b96a861ce4a309a328fe44342365349456ad7c654f +89ae3ce4b0357044579ca17be85d8361bb1ce3941f87e82077dd67e43ec0f95edd4bd3426225c90994a81a99e79490b7 +a170892074016d57c9d8e5a529379d7e08d2c1158b9ac4487ac9b95266c4fd51cb18ae768a2f74840137eec05000dd5a +aafd8acd1071103c7af8828a7a08076324d41ea530df90f7d98fafb19735fc27ead91b50c2ca45851545b41d589d0f77 +8623c849e61d8f1696dc9752116a26c8503fd36e2cbbc9650feffdd3a083d8cdbb3b2a4e9743a84b9b2ad91ac33083f2 +ac7166ddd253bb22cdbd8f15b0933c001d1e8bc295e7c38dc1d2be30220e88e2155ecd2274e79848087c05e137e64d01 +a5276b216d3df3273bbfa46210b63b84cfe1e599e9e5d87c4e2e9d58666ecf1af66cb7ae65caebbe74b6806677215bd0 +88792f4aa3597bb0aebadb70f52ee8e9db0f7a9d74f398908024ddda4431221a7783e060e0a93bf1f6338af3d9b18f68 +8f5fafff3ecb3aad94787d1b358ab7d232ded49b15b3636b585aa54212f97dc1d6d567c180682cca895d9876cacb7833 +ab7cb1337290842b33e936162c781aa1093565e1a5b618d1c4d87dd866daea5cebbcc486aaa93d8b8542a27d2f8694c7 +88480a6827699da98642152ebc89941d54b4791fbc66110b7632fb57a5b7d7e79943c19a4b579177c6cf901769563f2f +a725ee6d201b3a610ede3459660658ee391803f770acc639cfc402d1667721089fb24e7598f00e49e81e50d9fd8c2423 +98924372da8aca0f67c8c5cad30fa5324519b014fae7849001dcd51b6286118f12b6c49061219c37714e11142b4d46de +a62c27360221b1a7c99697010dfe1fb31ceb17d3291cf2172624ebeff090cbaa3c3b01ec89fe106dace61d934711d42d +825173c3080be62cfdc50256c3f06fe190bc5f190d0eb827d0af5b99d80936e284a4155b46c0d462ee574fe31d60983d +a28980b97023f9595fadf404ed4aa36898d404fe611c32fd66b70252f01618896f5f3fda71aea5595591176aabf0c619 +a50f5f9def2114f6424ff298f3b128068438f40860c2b44e9a6666f43c438f1780be73cf3de884846f1ba67f9bef0802 +b1eee2d730da715543aeb87f104aff6122cb2bf11de15d2519ff082671330a746445777924521ec98568635f26988d0c +862f6994a1ff4adfd9fb021925cccf542fca4d4b0b80fb794f97e1eb2964ef355608a98eec6e07aadd4b45ee625b2a21 +8ce69a18df2f9b9f6e94a456a7d94842c61dea9b00892da7cf5c08144de9be39b8c304aeca8b2e4222f87ba367e61006 +b5f325b1cecd435f5346b6bc562d92f264f1a6d91be41d612df012684fdd69e86063db077bc11ea4e22c5f2a13ae7bee +85526870a911127835446cb83db8986b12d5637d59e0f139ad6501ac949a397a6c73bd2e7fba731b1bb357efe068242c +8552247d3f7778697f77389717def5a149fc20f677914048e1ed41553b039b5427badc930491c0bae663e67668038fd1 +a545640ee5e51f3fe5de7050e914cfe216202056cd9d642c90e89a166566f909ee575353cb43a331fde17f1c9021414e +8b51229b53cff887d4cab573ba32ec52668d197c084414a9ee5589b285481cea0c3604a50ec133105f661321c3ca50f5 +8cdc0b960522bed284d5c88b1532142863d97bbb7dc344a846dc120397570f7bd507ceb15ed97964d6a80eccfef0f28e +a40683961b0812d9d53906e795e6470addc1f30d09affebf5d4fbbd21ddfa88ce441ca5ea99c33fd121405be3f7a3757 +a527875eb2b99b4185998b5d4cf97dd0d4a937724b6ad170411fc8e2ec80f6cee2050f0dd2e6fee9a2b77252d98b9e64 +84f3a75f477c4bc4574f16ebc21aaa32924c41ced435703c4bf07c9119dd2b6e066e0c276ff902069887793378f779e0 +a3544bc22d1d0cab2d22d44ced8f7484bfe391b36991b87010394bfd5012f75d580596ffd4f42b00886749457bb6334b +b81f6eb26934b920285acc20ceef0220dd23081ba1b26e22b365d3165ce2fbae733bbc896bd0932f63dcc84f56428c68 +95e94d40a4f41090185a77bf760915a90b6a3e3ace5e53f0cb08386d438d3aa3479f0cd81081b47a9b718698817265cd +b69bd1625b3d6c17fd1f87ac6e86efa0d0d8abb69f8355a08739109831baeec03fd3cd4c765b5ff8b1e449d33d050504 +8448f4e4c043519d98552c2573b76eebf2483b82d32abb3e2bfc64a538e79e4f59c6ca92adff1e78b2f9d0a91f19e619 +8f11c42d6a221d1fda50887fb68b15acdb46979ab21d909ed529bcad6ae10a66228ff521a54a42aca0dad6547a528233 +a3adb18d7e4a882b13a067784cf80ea96a1d90f5edc61227d1f6e4da560c627688bdf6555d33fe54cab1bca242986871 +a24d333d807a48dc851932ed21cbdd7e255bad2699909234f1706ba55dea4bb6b6f8812ffc0be206755868ba8a4af3f9 +a322de66c22a606e189f7734dbb7fda5d75766d5e69ec04b4e1671d4477f5bcb9ff139ccc18879980ebc3b64ab4a2c49 +88f54b6b410a1edbf125db738d46ee1a507e69bc5a8f2f443eb787b9aa7dbd6e55014ec1e946aabeb3e27a788914fb04 +b32ee6da1dcd8d0a7fd7c1821bb1f1fe919c8922b4c1eeed56e5b068a5a6e68457c42b192cbaef5dc6d49b17fa45bc0f +8a44402da0b3a15c97b0f15db63e460506cb8bef56c457166aea5e8881087d8202724c539ef0feb97131919a73aefca8 +b967e3fead6171fa1d19fd976535d428b501baff59e118050f9901a54b12cc8e4606348454c8f0fc25bd6644e0a5532e +b7a0c9e9371c3efbbb2c6783ce2cc5f149135175f25b6d79b09c808bce74139020e77f0c616fa6dcb3d87a378532529d +a54207782ffc909cd1bb685a3aafabbc4407cda362d7b3c1b14608b6427e1696817aeb4f3f85304ac36e86d3d8caa65b +98c1da056813a7bfebc81d8db7206e3ef9b51f147d9948c088976755826cc5123c239ca5e3fe59bed18b5d0a982f3c3f +ae1c86174dfafa9c9546b17b8201719aecd359f5bbeb1900475041f2d5b8a9600d54d0000c43dd061cfda390585726ff +a8ee5a8be0bd1372a35675c87bfd64221c6696dc16e2d5e0996e481fec5cdbcb222df466c24740331d60f0521285f7d3 +8ddadbe3cf13af50d556ce8fc0dd77971ac83fad9985c3d089b1b02d1e3afc330628635a31707b32595626798ea22d45 +a5c80254baf8a1628dc77c2445ebe21fbda0de09dd458f603e6a9851071b2b7438fe74214df293dfa242c715d4375c95 +b9d83227ed2600a55cb74a7052003a317a85ca4bea50aa3e0570f4982b6fe678e464cc5156be1bd5e7bba722f95e92c5 +b56085f9f3a72bea9aa3a8dc143a96dd78513fa327b4b9ba26d475c088116cab13843c2bff80996bf3b43d3e2bddb1d6 +8fa9b39558c69a9757f1e7bc3f07295e4a433da3e6dd8c0282397d26f64c1ecd8eb3ba9824a7cacfb87496ebbb45d962 +879c6d0cb675812ed9dee68c3479a499f088068501e2677caeae035e6f538da91a49e245f5fcce135066169649872bee +91aa9fd3fed0c2a23d1edda8a6542188aeb8abee8772818769bdee4b512d431e4625a343af5d59767c468779222cf234 +a6be0bb2348c35c4143482c7ef6da9a93a5356f8545e8e9d791d6c08ed55f14d790d21ee61d3a56a2ae7f888a8fd46ca +808ee396a94e1b8755f2b13a6ffbedef9e0369e6c2e53627c9f60130c137299d0e4924d8ef367e0a7fad7f68a8c9193c +ad1086028fcdac94d5f1e7629071e7e47e30ad0190ae59aaebfb7a7ef6202ab91323a503c527e3226a23d7937af41a52 +9102bdaf79b907d1b25b2ec6b497e2d301c8eac305e848c6276b392f0ad734131a39cc02ed42989a53ca8da3d6839172 +8c976c48a45b6bc7cd7a7acea3c2d7c5f43042863b0661d5cd8763e8b50730552187a8eecf6b3d17be89110208808e77 +a2624c7e917e8297faa3af89b701953006bf02b7c95dfba00c9f3de77748bc0b13d6e15bb8d01377f4d98fb189538142 +a405f1e66783cdcfe20081bce34623ec3660950222d50b7255f8b3cc5d4369aeb366e265e5224c0204911539f0fa165e +8d69bdcaa5d883b5636ac8f8842026fcc58c5e2b71b7349844a3f5d6fbecf44443ef4f768eac376f57fb763606e92c9f +82fce0643017d16ec1c3543db95fb57bfa4855cc325f186d109539fcacf8ea15539be7c4855594d4f6dc628f5ad8a7b0 +8860e6ff58b3e8f9ae294ff2487f0d3ffae4cf54fd3e69931662dabc8efd5b237b26b3def3bcd4042869d5087d22afcf +88c80c442251e11c558771f0484f56dc0ed1b7340757893a49acbf96006aa73dfc3668208abea6f65375611278afb02a +8be3d18c6b4aa8e56fcd74a2aacb76f80b518a360814f71edb9ccf3d144bfd247c03f77500f728a62fca7a2e45e504c5 +8b8ebf0df95c3f9b1c9b80469dc0d323784fd4a53f5c5357bb3f250a135f4619498af5700fe54ad08744576588b3dfff +a8d88abdaadd9c2a66bc8db3072032f63ed8f928d64fdb5f810a65074efc7e830d56e0e738175579f6660738b92d0c65 +a0a10b5d1a525eb846b36357983c6b816b8c387d3890af62efb20f50b1cb6dd69549bbef14dab939f1213118a1ae8ec2 +8aadf9b895aeb8fdc9987daa937e25d6964cbd5ec5d176f5cdf2f0c73f6f145f0f9759e7560ab740bf623a3279736c37 +99aeda8a495031cc5bdf9b842a4d7647c55004576a0edc0bd9b985d60182608361ed5459a9d4b21aa8e2bd353d10a086 +832c8b3bfcd6e68eee4b100d58014522de9d4cefa99498bc06c6dca83741e4572e20778e0d846884b33439f160932bca +841f56ebefc0823ab484fc445d62f914e13957e47904419e42771aa605e33ab16c44f781f6f9aa42e3a1baf377f54b42 +a6e40271d419e295a182725d3a9b541ffd343f23e37549c51ecaa20d13cf0c8d282d6d15b24def5702bfee8ba10b12ac +8ac00925ac6187a4c5cde48ea2a4eaf99a607e58b2c617ee6f01df30d03fafada2f0469178dd960d9d64cbd33a0087d8 +b6b80916b540f8a0fe4f23b1a06e2b830008ad138271d5ba3cd16d6619e521fe2a7623c16c41cba48950793386eea942 +8412c0857b96a650e73af9d93087d4109dd092ddf82188e514f18fcac644f44d4d62550bfa63947f2d574a2e9d995bbb +b871395baa28b857e992a28ac7f6d95ec461934b120a688a387e78498eb26a15913b0228488c3e2360391c6b7260b504 +926e2d25c58c679be77d0e27ec3b580645956ba6f13adcbc2ea548ee1b7925c61fcf74c582337a3b999e5427b3f752f2 +a165fa43fecae9b913d5dcfc232568e3e7b8b320ce96b13800035d52844c38fd5dbf7c4d564241d860c023049de4bcbc +b4976d7572fd9cc0ee3f24888634433f725230a7a2159405946a79315bc19e2fc371448c1c9d52bf91539fd1fe39574b +a6b461eb72e07a9e859b9e16dfa5907f4ac92a5a7ca4368b518e4a508dc43f9b4be59db6849739f3ef4c44967b63b103 +b976606d3089345d0bc501a43525d9dca59cf0b25b50dfc8a61c5bd30fac2467331f0638fab2dc68838aa6ee8d2b6bc9 +b16ea61c855da96e180abf7647fa4d9dd6fd90adebadb4c5ed4d7cd24737e500212628fca69615d89cb40e9826e5a214 +95a3e3162eb5ea27a613f8c188f2e0dcc5cbd5b68c239858b989b004d87113e6aa3209fa9fad0ee6ecef42814ba9db1a +b6a026ab56d3224220e5bce8275d023c8d39d1bdf7eec3b0923429b7d5ef18cf613a3591d364be8727bb1fa0ba11eabb +949f117e2e141e25972ee9ccdd0b7a21150de7bbf92bbd89624a0c5f5a88da7b2b172ba2e9e94e1768081f260c2a2f8d +b7c5e9e6630287d2a20a2dfb783ffe6a6ff104ff627c6e4e4342acc2f3eb6e60e9c22f465f8a8dc58c42f49840eca435 +872be5a75c3b85de21447bb06ac9eb610f3a80759f516a2f99304930ddf921f34cbffc7727989cdd7181d5fc62483954 +a50976ea5297d797d220932856afdd214d1248230c9dcd840469ecc28ea9f305b6d7b38339fedb0c00b5251d77af8c95 +80b360f8b44914ff6f0ffbd8b5360e3cabe08639f6fe06d0c1526b1fe9fe9f18c497f1752580b30e950abd3e538ad416 +a2f98f9bf7fac78c9da6bb41de267742a9d31cf5a04b2fb74f551084ec329b376f651a59e1ae919b2928286fb566e495 +8b9d218a8a6c150631548e7f24bbd43f132431ae275c2b72676abbea752f554789c5ff4aac5c0eeee5529af7f2b509ef +aa21a243b07e9c7b169598bf0b102c3c280861780f83121b2ef543b780d47aaa4b1850430ee7927f33ece9847c4e0e1a +8a6f90f4ce58c8aa5d3656fe4e05acccf07a6ec188a5f3cde7bf59a8ae468e66f055ac6dfc50b6e8e98f2490d8deedc5 +8e39f77ca4b5149ffe9945ceac35d068760ba338d469d57c14f626dd8c96dbe993dd7011beff727c32117298c95ee854 +83bd641c76504222880183edd42267e0582642c4993fe2c7a20ce7168e4c3cbf7586e1d2d4b08c84d9b0bf2f6b8800b8 +a9d332993cf0c1c55130e5cf3a478eb5e0bfb49c25c07538accc692ef03d82b458750a7b991cc0b41b813d361a5d31e3 +a0fc60e6a6015df9bee04cea8f20f01d02b14b6f7aa03123ab8d65da071b2d0df5012c2a69e7290baae6ed6dd29ebe07 +a2949dde2e48788ceaac7ec7243f287ffe7c3e788cdba97a4ab0772202aeef2d50382bed8bf7eff5478243f7eabe0bda +a7879373ea18572dba6cf29868ca955ffa55b8af627f29862f6487ee398b81fe3771d8721ca8e06716c5d91b9ac587cb +b3c7081e2c5306303524fbe9fe5645111a57dffd4ec25b7384da12e56376a0150ab52f9d9cc6ca7bdd950695e39b766d +a634a6a19d52dcb9f823352b36c345d2de54b75197bcd90528d27830bd6606d1a9971170de0849ed5010afa9f031d5be +88f2062f405fa181cfdb8475eaf52906587382c666ca09a9522537cfebbc7de8337be12a7fd0db6d6f2f7ab5aefab892 +b1f0058c1f273191247b98783b2a6f5aa716cf799a8370627fc3456683f03a624d0523b63a154fe9243c0dfd5b37c460 +ae39a227cc05852437d87be6a446782c3d7fbe6282e25cf57b6b6e12b189bdc0d4a6e2c3a60b3979256b6b5baf8f1c5f +802a1af228ab0c053b940e695e7ef3338f5be7acf4e5ed01ac8498e55b492d3a9f07996b1700a84e22f0b589638909cd +a36490832f20e4b2f9e79ee358b66d413f034d6a387534b264cdeac2bca96e8b5bcbdd28d1e98c44498032a8e63d94d2 +8728c9a87db2d006855cb304bba54c3c704bf8f1228ae53a8da66ca93b2dac7e980a2a74f402f22b9bc40cd726e9c438 +a08f08ab0c0a1340e53b3592635e256d0025c4700559939aeb9010ed63f7047c8021b4210088f3605f5c14fb51d1c613 +9670fd7e2d90f241e8e05f9f0b475aa260a5fb99aa1c9e61cd023cbad8ed1270ae912f168e1170e62a0f6d319cf45f49 +a35e60f2dd04f098bf274d2999c3447730fe3e54a8aff703bc5a3c274d22f97db4104d61a37417d93d52276b27ef8f31 +859df7a21bc35daec5695201bd69333dc4f0f9e4328f2b75a223e6615b22b29d63b44d338413ca97eb74f15563628cb7 +b2b44ad3e93bc076548acdf2477803203108b89ecc1d0a19c3fb9814d6b342afc420c20f75e9c2188ad75fdb0d34bb2d +941173ee2c87765d10758746d103b667b1227301e1bcfecef2f38f9ab612496a9abd3050cef5537bf28cfecd2aacc449 +92b0bea30ebed20ac30648efb37bac2b865daaa514316e6f5470e1de6cb84651ff77c127aa7beed4521bda5e8fc81122 +af17bf813bb238cf8bb437433f816786612209180a6c0a1d5141292dc2d2c37164ef13bfc50c718bfcc6ce26369298a2 +8461fd951bdfda099318e05cc6f75698784b033f15a71bce26165f0ce421fd632d50df9eeced474838c0050b596e672c +83281aa18ae4b01e8201e1f64248cc6444c92ee846ae72adb178cef356531558597d84ff93a05abf76bfe313eb7dbe86 +b62b150f73999c341daa4d2f7328d2f6ca1ef3b549e01df58182e42927537fc7971c360fe8264af724f4c0247850ef12 +a7022a201f79c012f982b574c714d813064838a04f56964d1186691413757befeeaada063e7884297606e0eea1b1ed43 +a42ac9e8be88e143853fd8e6a9ff21a0461801f0ac76b69cca669597f9af17ecb62cccdcdcbe7f19b62ab93d7f838406 +80f1ca73b6ba3a2fbae6b79b39c0be8c39df81862d46c4990c87cbf45b87996db7859d833abc20af2fcb4faf059c436a +b355943e04132d5521d7bbe49aea26f6aa1c32f5d0853e77cc2400595325e923a82e0ff7601d1aee79f45fd8a254f6ae +87142c891d93e539b31d0b5ead9ea600b9c84db9be9369ff150a8312fe3d10513f4c5b4d483a82b42bc65c45dd9dd3bd +823c3d7f6dda98a9d8c42b3fee28d3154a95451402accadb6cf75fc45d2653c46a569be75a433094fa9e09c0d5cf1c90 +b3c3497fe7356525c1336435976e79ec59c5624c2fb6185ee09ca0510d58b1e392965e25df8a74d90d464c4e8bb1422b +88c48d83e8ddc0d7eea051f3d0e21bc0d3a0bb2b6a39ece76750c1c90c382a538c9a35dc9478b8ceb8157dcccbbf187a +93da81a8939f5f58b668fefdc6f5f7eca6dc1133054de4910b651f8b4a3267af1e44d5a1c9e5964dc7ab741eb146894b +8b396e64985451ac337f16be61105106e262e381ea04660add0b032409b986e1ac64da3bc2feae788e24e9cb431d8668 +9472068b6e331ea67e9b5fbf8057672da93c209d7ded51e2914dbb98dccd8c72b7079b51fd97a7190f8fc8712c431538 +ac47e1446cb92b0a7406f45c708567f520900dfa0070d5e91783139d1bfc946d6e242e2c7b3bf4020500b9f867139709 +896053706869fb26bb6f7933b3d9c7dd6db5c6bd1269c7a0e222b73039e2327d44bda7d7ae82bf5988808b9831d78bcd +a55e397fa7a02321a9fe686654c86083ecedb5757586d7c0250ec813ca6d37151a12061d5feca4691a0fd59d2f0fdd81 +ae23f08ac2b370d845036518f1bddb7fea8dc59371c288a6af310486effeb61963f2eef031ca90f9bdbcf0e475b67068 +b5462921597a79f66c0fec8d4c7cfd89f427692a7ce30d787e6fd6acd2377f238ec74689a0fdbe8ef3c9c9bd24b908dc +ae67e8ea7c46e29e6aae6005131c29472768326819aa294aaf5a280d877de377b44959adb1348fa3e929dcbc3ae1f2c0 +84962b4c66500a20c4424191bdfb619a46cda35bdb34c2d61edcb0b0494f7f61dd5bf8f743302842026b7b7d49edd4b5 +846f76286dc3cc59cb15e5dabb72a54a27c78190631df832d3649b2952fa0408ecde7d4dfdae7046c728efa29879fb51 +8f76c854eaee8b699547e07ad286f7dadfa6974c1328d12502bd7630ae619f6129272fdd15e2137ffef0143c42730977 +8007b163d4ea4ec6d79e7a2aa19d06f388da0b3a56f3ee121441584e22a246c0e792431655632bf6e5e02cb86914eebf +ac4d2cecc1f33e6fb73892980b61e62095ddff5fd6167f53ca93d507328b3c05440729a277dc3649302045b734398af1 +92d2a88f2e9c9875abaff0d42624ccb6d65401de7127b5d42c25e6adccd7a664504c5861618f9031ced8aeb08b779f06 +a832c1821c1b220eb003fc532af02c81196e98df058cdcc9c9748832558362915ea77526937f30a2f74f25073cb89afb +b6f947ab4cc2baec100ed8ec7739a2fd2f9504c982b39ab84a4516015ca56aea8eef5545cfc057dd44c69b42125fb718 +b24afacf2e90da067e5c050d2a63878ee17aaf8fd446536f2462da4f162de87b7544e92c410d35bf2172465940c19349 +b7a0aa92deac71eaab07be8fa43086e071e5580f5dbf9b624427bdd7764605d27303ae86e5165bed30229c0c11958c38 +b0d1d5bfa1823392c5cf6ed927c1b9e84a09a24b284c2cd8fcb5fda8e392c7c59412d8f74eb7c48c6851dff23ae66f58 +a24125ef03a92d2279fb384186ca0274373509cfec90b34a575490486098438932ee1be0334262d22d5f7d3db91efe67 +83e08e5fba9e8e11c164373794f4067b9b472d54f57f4dbe3c241cf7b5b7374102de9d458018a8c51ab3aed1dddf146f +9453101b77bb915ed40990e1e1d2c08ea8ec5deb5b571b0c50d45d1c55c2e2512ec0ceca616ff0376a65678a961d344d +92a0516e9eb6ad233d6b165a8d64a062ce189b25f95d1b3264d6b58da9c8d17da2cd1f534800c43efcf2be73556cd2ff +958d0b5d7d8faf25d2816aa6a2c5770592ad448db778dd9b374085baa66c755b129822632eaabcb65ee35f0bf4b73634 +90a749de8728b301ad2a6b044e8c5fd646ccd8d20220e125cba97667e0bb1d0a62f6e3143b28f3d93f69cdc6aa04122a +84bd34c8d8f74dec07595812058db24d62133c11afed5eb2a8320d3bfc28e442c7f0cfd51011b7b0bb3e5409cb7b6290 +aecc250b556115d97b553ad7b2153f1d69e543e087890000eaa60f4368b736921d0342ce5563124f129096f5d5e2ca9d +977f17ac82ed1fbf422f9b95feb3047a182a27b00960296d804fd74d54bb39ad2c055e665c1240d2ad2e06a3d7501b00 +af5be9846bd4879ebe0af5e7ad253a632f05aedfe306d31fe6debe701ba5aa4e33b65efc05043bc73aadb199f94baed4 +9199e12ec5f2aaaeed6db5561d2dcc1a8fe9c0854f1a069cba090d2dff5e5ba52b10c841ccbd49006a91d881f206150d +8f4a96a96ed8ceaf3beba026c89848c9ca4e6452ce23b7cf34d12f9cc532984a498e051de77745bdc17c7c44c31b7c30 +af3f2a3dbe8652c4bfca0d37fb723f0e66aab4f91b91a625114af1377ad923da8d36da83f75deb7a3219cd63135a3118 +a6d46963195df8962f7aa791d104c709c38caa438ddd192f7647a884282e81f748c94cdf0bb25d38a7b0dc1b1d7bbcf7 +86f3de4b22c42d3e4b24b16e6e8033e60120af341781ab70ae390cb7b5c5216f6e7945313c2e04261a51814a8cb5db92 +b9f86792e3922896cfd847d8ff123ff8d69ecf34968fb3de3f54532f6cd1112b5d34eeabdca46ae64ad9f6e7e5b55edc +83edfbcbc4968381d1e91ab813b3c74ab940eaf6358c226f79182f8b21148ec130685fd91b0ea65916b0a50bccf524ea +93b61daca7a8880b7926398760f50016f2558b0bab74c21181280a1baf3414fc539911bb0b79c4288d29d3c4ad0f4417 +ad541aeb83a47526d38f2e47a5ce7e23a9adabe5efeae03541026881e6d5ef07da3ac1a6ed466ca924fa8e7a91fcff88 +ac4bba31723875025640ed6426003ed8529215a44c9ffd44f37e928feef9fc4dfa889088131c9be3da87e8f3fdf55975 +88fa4d49096586bc9d29592909c38ea3def24629feacd378cc5335b70d13814d6dac415f8c699ee1bf4fe8b85eb89b38 +b67d0b76cbd0d79b71f4673b96e77b6cda516b8faa1510cfe58ff38cc19000bb5d73ff8418b3dab8c1c7960cb9c81e36 +98b4f8766810f0cfecf67bd59f8c58989eb66c07d3dfeee4f4bbce8fd1fce7cc4f69468372eaec7d690748543bd9691d +8445891af3c298b588dec443beacdf41536adb84c812c413a2b843fd398e484eb379075c64066b460839b5fe8f80177c +b603635c3ed6fdc013e2a091fc5164e09acf5f6a00347d87c6ebadb1f44e52ff1a5f0466b91f3f7ffc47d25753e44b75 +87ec2fc928174599a9dafe7538fec7dcf72e6873b17d953ed50708afff0da37653758b52b7cafa0bf50dfcf1eafbb46c +b9dbd0e704d047a457d60efe6822dc679e79846e4cbcb11fa6c02079d65673ee19bbf0d14e8b7b200b9205f4738df7c7 +9591ec7080f3f5ba11197a41f476f9ba17880f414d74f821a072ec5061eab040a2acba3d9856ff8555dfe5eaeb14ca19 +b34c9d1805b5f1ce38a42b800dec4e7f3eb8c38e7d2b0a525378e048426fed150dbfe9cc61f5db82b406d1b9ff2d10bf +a36fdc649dc08f059dfa361e3969d96b4cc4a1ebf10b0cd01a7dd708430979e8d870961fef85878f8779b8e23caafb18 +88dfc739a80c16c95d9d6f73c3357a92d82fa8c3c670c72bee0f1e4bac9ec338e1751eb786eda3e10f747dd7a686900f +84a535ad04f0961756c61c70001903a9adf13126983c11709430a18133c4b4040d17a33765b4a06968f5d536f4bfb5c5 +8c86d695052a2d2571c5ace744f2239840ef21bb88e742f050c7fa737cd925418ecef0971333eb89daa6b3ddfede268c +8e9a700157069dc91e08ddcbdde3a9ad570272ad225844238f1015004239c542fceb0acce6d116c292a55f0d55b6175e +84d659e7f94e4c1d15526f47bc5877a4ef761c2a5f76ec8b09c3a9a30992d41b0e2e38ed0c0106a6b6c86d670c4235f3 +a99253d45d7863db1d27c0ab561fb85da8c025ba578b4b165528d0f20c511a9ca9aff722f4ff7004843f618eb8fced95 +89a3cacb15b84b20e95cd6135550146bbe6c47632cc6d6e14d825a0c79b1e02b66f05d57d1260cb947dc4ae5b0283882 +8385b1555e794801226c44bd5e878cbe68aeac0a19315625a8e5ea0c3526b58cdd4f53f9a14a167a5e8a293b530d615a +b68c729e9df66c5cd22af4909fb3b0057b6a231c4a31cd6bf0fa0e53c5809419d15feb483de6e9408b052458e819b097 +924f56eda269ec7ec2fc20c5731bf7f521546ddf573ccbe145592f1c9fee5134747eb648d9335119a8066ca50a1f7e50 +b2100a26b9c3bec7ec5a53f0febbf56303f199be2f26b2d564cfee2adc65483b84192354f2865c2f4c035fa16252ae55 +8f64dbed62e638563967ec1605a83216aed17eb99aa618c0543d74771ea8f60bbb850c88608d4f8584f922e30a8a0a72 +b31b9e1ffe8d7260479c9413f8e680f3fe391ae8fcf44fcca3000d9b2473a40c1d32299f8f63865a57579a2d6c7e9f08 +a5b1d136142eb23e322c6c07cb838a3f58ab6925472352ebd0bb47041a0d8729e1074ca223922f3a7a672ced7a1e562d +8d9470a5a15d833a447b5f108333d50f30aa7659e331c3f8080b1e928a99922edc650466a2f54f3d48afdb34bff42142 +866368f5891564e5b2de37ad21ff0345c01129a14ea5667f9b64aad12d13ec034622872e414743af0bf20adb2041b497 +88ef9c2ebf25fd0c04b7cfa35fbac2e4156d2f1043fa9f98998b2aa402c8f9a4f1039e782451a46840f3e0e4b3fa47d3 +94ba04a4859273697e264a2d238dc5c9ff573ebc91e4796ea58eebe4080c1bf991255ab2ad8fb1e0301ce7b79cc6e69b +86b6bd0953309a086e526211bf1a99327269304aa74d8cdc994cee63c3a2d4b883e832b0635888dff2a13f1b02eb8df4 +843ea6ea5f2c7a1fd50be56a5765dcce3ea61c99b77c1a729ee0cd8ec706385ac7062e603479d4c8d3527f030762d049 +8d3675195a3b06f2d935d45becc59f9fa8fa440c8df80c029775e47fe9c90e20f7c8e4cc9a2542dd6bfe87536c428f0d +8978580b0c9b0aa3ab2d47e3cfd92fa891d3ddee57829ee4f9780e8e651900457d8e759d1a9b3e8f6ae366e4b57f2865 +890112ec81d0f24b0dfbb4d228e418eff02ae63dc691caf59c1d103e1d194e6e2550e1bec41c0bfdb74fed454f621d0c +97da00bd4b19d1e88caff7f95b8b9a7d29bc0afe85d0c6a163b4b9ef336f0e90e2c49ce6777024bb08df908cc04ea1ca +b458268d275a5211106ccaa8333ce796ef2939b1c4517e502b6462e1f904b41184a89c3954e7c4f933d68b87427a7bfd +aac9c043ba8ba9283e8428044e6459f982413380ee7005a996dc3cc468f6a21001ecaa3b845ce2e73644c2e721940033 +82145013c2155a1200246a1e8720adf8a1d1436b10d0854369d5b1b6208353e484dd16ce59280c6be84a223f2d45e5e2 +b301bafa041f9b203a46beab5f16160d463aa92117c77a3dc6a9261a35645991b9bafcc186c8891ca95021bd35f7f971 +a531b8d2ac3de09b92080a8d8857efa48fb6a048595279110e5104fee7db1dd7f3cfb8a9c45c0ed981cbad101082e335 +a22ac1d627d08a32a8abd41504b5222047c87d558ffae4232cefdeb6a3dc2a8671a4d8ddfba2ff9068a9a3ffb0fe99b1 +b8d9f0e383c35afb6d69be7ff04f31e25c74dd5751f0e51290c18814fbb49ee1486649e64355c80e93a3d9278bd21229 +8165babccd13033a3614c878be749dfa1087ecbeee8e95abcfffe3aa06695711122cb94477a4d55cffd2febf0c1173de +a4c1bc84ecb9d995d1d21c2804adf25621676d60334bd359dac3a2ec5dc8de567aa2831c10147034025fb3e3afb33c4b +b77307cab8e7cb21e4038493058fb6db9e2ec91dda9d7f96f25acbc90309daf7b6d8a205682143ee35d675e9800c3b08 +aaf7466083cd1f325ba860efe3faf4cebe6a5eecf52c3e8375d72043a5cfc8e6cb4b40f8e48f97266e84f0d488e8badf +9264a05a3abc2a5b4958f957f3a486a5eb3ddd10ff57aa6943c9430d0cfa01d63b72695b1ade50ac1b302d312175e702 +b3f9e4c589ad28b1eceed99dc9980fac832524cfcbe4a486dfeedb4b97c080e24bdb3967e9ca63d2240e77f9addfaefd +b2c1e253a78e7179e5d67204422e0debfa09c231970b1bfb70f31a8d77c7f5059a095ca79d2e9830f12c4a8f88881516 +81865a8a25913d1072cb5fd9505c73e0fde45e4c781ddd20fb0a7560d8b1cd5e1f63881c6efc05360e9204dfa6c3ce16 +ab71c2ea7fa7853469a2236dedb344a19a6130dc96d5fd6d87d42d3fffda172557d203b7688ce0f86acd913ce362e6cd +8aa2051bc3926c7bd63565f3782e6f77da824cb3b22bb056aa1c5bccfa274c0d9e49a91df62d0e88876e2bd7776e44b9 +b94e7074167745323d1d353efe7cfb71f40a390e0232354d5dfd041ef523ac8f118fb6dcc42bf16c796e3f61258f36f8 +8210fcf01267300cb1ccf650679cf6e1ee46df24ae4be5364c5ff715332746c113d680c9a8be3f17cacaeb3a7ba226ce +905ac223568eedc5acd8b54e892be05a21abbb4083c5dbec919129f9d9ffa2c4661d78d43bf5656d8d7aafa06f89d647 +a6e93da7e0c998e6ce2592d1aa87d12bf44e71bec12b825139d56682cdce8f0ba6dbfe9441a9989e10578479351a3d9d +acde928a5e2df0d65de595288f2b81838155d5673013100a49b0cb0eb3d633237af1378148539e33ccd1b9a897f0fec3 +a6e1a47e77f0114be6ae7acd2a51e6a9e38415cce7726373988153cdd5d4f86ef58f3309adc5681af4a159300ed4e5b5 +ad2b6a0d72f454054cb0c2ebc42cd59ff2da7990526bd4c9886003ba63b1302a8343628b8fe3295d3a15aa85150e0969 +b0bc3aea89428d7918c2ee0cc57f159fba134dad224d0e72d21a359ca75b08fbb4373542f57a6408352033e1769f72c6 +aad0497525163b572f135fad23fdd8763631f11deeaf61dea5c423f784fe1449c866040f303555920dc25e39cdb2e9b4 +8ce5d8310d2e17342bf881d517c9afc484d12e1f4b4b08ad026b023d98cba410cd9a7cc8e2c3c63456652a19278b6960 +8d9d57dbb24d68b6152337872bd5d422198da773174ade94b633f7c7f27670ff91969579583532ae7d8fe662c6d8a3b0 +855a1c2d83becb3f02a8f9a83519d1cb112102b61d4cdd396844b5206e606b3fefdbcc5aa8751da2b256d987d74d9506 +90eb7e6f938651f733cf81fcd2e7e8f611b627f8d94d4ac17ac00de6c2b841e4f80cada07f4063a13ae87b4a7736ca28 +8161459a21d55e7f5f1cecfc1595c7f468406a82080bfa46d7fb1af4b5ec0cd2064c2c851949483db2aa376e9df418e6 +8344ccd322b2072479f8db2ab3e46df89f536408cba0596f1e4ec6c1957ff0c73f3840990f9028ae0f21c1e9a729d7df +929be2190ddd54a5afe98c3b77591d1eae0ab2c9816dc6fe47508d9863d58f1ea029d503938c8d9e387c5e80047d6f1e +856e3d1f701688c650c258fecd78139ce68e19de5198cf1cd7bb11eba9d0f1c5af958884f58df10e3f9a08d8843f3406 +8490ae5221e27a45a37ca97d99a19a8867bcc026a94f08bdccfbb4b6fa09b83c96b37ec7e0fd6ee05f4ae6141b6b64a8 +b02dbd4d647a05ac248fda13708bba0d6a9cd00cae5634c1938b4c0abbb3a1e4f00f47aa416dcd00ffcdf166330bff9a +9076164bb99ca7b1a98d1e11cb2f965f5c22866658e8259445589b80e3cb3119c8710ede18f396ba902696785619079c +aacf016920936dae63778ad171386f996f65fe98e83cfcdd75e23774f189303e65cc8ad334a7a62f9230ed2c6b7f6fa4 +a8031d46c7f2474789123469ef42e81c9c35eb245d38d8f4796bba406c02b57053f5ec554d45373ab437869a0b1af3f0 +a4b76cd82dc1f305a0ee053e9a4212b67f5acc5e69962a8640d190a176b73fbc2b0644f896ff3927cd708d524668ed09 +b00b029c74e6fdf7fb94df95ef1ccad025c452c19cddb5dccfb91efdcb8a9a1c17847cfa4486eae4f510e8a6c1f0791a +9455e5235f29a73e9f1a707a97ddb104c55b9d6a92cc9952600d49f0447d38ea073ee5cf0d13f7f55f12b4a5132f4b10 +ae118847542ed1084d269e8f3b503d0b6571a2c077def116ad685dcca2fca3dcb3f86e3f244284bdcd5ae7ac968d08a5 +8dcb4965cd57e8b89cd71d6fc700d66caa805bfd29ab71357961527a7894e082d49145c2614b670dcb231ab9050d0663 +add6ed14f3183f4acc73feea19b22c9a330e431c674e5034924da31b69e8c02d79b570d12ef771a04215c4809e0f8a80 +96ae7e110412ee87d0478fdbdbaab290eb0b6edd741bb864961845e87fd44bcbe630371060b8104d8bf17c41f2e3fca0 +a20db17f384e9573ca0928af61affab6ff9dd244296b69b026d737f0c6cd28568846eca8dadf903ee0eecbb47368351d +937bfdf5feb0797863bc7c1be4dcc4f2423787952a3c77dfa3bfe7356f5dbcc4daebde976b84fc6bd97d5124fb8f85c9 +a7050cc780445c124e46bba1acc0347ddcfa09a85b35a52cc5808bf412c859c0c680c0a82218f15a6daeefe73f0d0309 +a9d9b93450e7630f1c018ea4e6a5ca4c19baa4b662eadfbe5c798fe798d8a3775ed1eb12bd96a458806b37ab82bdc10a +a52a4d5639e718380915daaefad7de60764d2d795443a3db7aeab5e16a1b8faa9441a4ccc6e809d8f78b0ac13eef3409 +8e6f72b6664a8433b032849b03af68f9376b3c16c0bc86842c43fc7bf31e40bc9fc105952d5c5780c4afa19d7b802caa +a107ae72f037000c6ee14093de8e9f2c92aa5f89a0a20007f4126419e5cb982469c32187e51a820f94805c9fccd51365 +9708218f9a984fe03abc4e699a4f3378a06530414a2e95e12ca657f031ef2e839c23fd83f96a4ba72f8203d54a1a1e82 +b9129770f4c5fcac999e98c171d67e148abd145e0bf2a36848eb18783bb98dff2c5cef8b7407f2af188de1fae9571b1c +88cc9db8ff27eb583871eeeb517db83039b85404d735517c0c850bdfa99ae1b57fd24cf661ab60b4726878c17e047f37 +a358c9aadc705a11722df49f90b17a2a6ba057b2e652246dc6131aaf23af66c1ca4ac0d5f11073a304f1a1b006bc0aa5 +ac79f25af6364a013ba9b82175ccee143309832df8f9c3f62c193660253679284624e38196733fb2af733488ab1a556e +82338e3ed162274d41a1783f44ae53329610134e6c62565353fbcc81131e88ce9f8a729d01e59e6d73695a378315111b +aa5ddcabf580fd43b6b0c3c8be45ffd26c9de8fa8d4546bb92d34f05469642b92a237d0806a1ad354f3046a4fcf14a92 +b308d2c292052a8e17862c52710140ffafa0b3dbedd6a1b6334934b059fe03e49883529d6baf8b361c6e67b3fbf70100 +96d870a15c833dddd8545b695139733d4a4c07d6206771a1524500c12607048731c49ec4ac26f5acc92dd9b974b2172c +8e99ee9ed51956d05faaf5038bffd48a2957917a76d9974a78df6c1ff3c5423c5d346778f55de07098b578ad623a390e +a19052d0b4b89b26172c292bbf6fd73e7486e7fd3a63c7a501bbd5cf7244e8e8ce3c1113624086b7cdf1a7693fdad8b5 +958957caf99dc4bb6d3c0bc4821be10e3a816bd0ba18094603b56d9d2d1383ccc3ee8bc36d2d0aea90c8a119d4457eb4 +8482589af6c3fc4aa0a07db201d8c0d750dd21ae5446ff7a2f44decf5bff50965fd6338745d179c67ea54095ecd3add4 +8a088cc12cf618761eaa93da12c9158b050c86f10cd9f865b451c69e076c7e5b5a023e2f91c2e1eed2b40746ca06a643 +85e81101590597d7671f606bd1d7d6220c80d3c62e9f20423e734482c94547714a6ac0307e86847cce91de46503c6a8a +b1bd39b481fc452d9abf0fcb73b48c501aaae1414c1c073499e079f719c4e034da1118da4ff5e0ce1c5a71d8af3f4279 +942ae5f64ac7a5353e1deb2213f68aa39daa16bff63eb5c69fc8d9260e59178c0452227b982005f720a3c858542246c8 +99fea18230e39df925f98e26ff03ab959cae7044d773de84647d105dfa75fd602b4f519c8e9d9f226ec0e0de0140e168 +97b9841af4efd2bfd56b9e7cd2275bc1b4ff5606728f1f2b6e24630dbe44bc96f4f2132f7103bca6c37057fc792aeaab +94cdad044a6ab29e646ed30022c6f9a30d259f38043afcea0feceef0edc5f45297770a30718cbfec5ae7d6137f55fe08 +a533a5efa74e67e429b736bb60f2ccab74d3919214351fe01f40a191e3ec321c61f54dd236f2d606c623ad556d9a8b63 +b7bd0bb72cd537660e081f420545f50a6751bb4dd25fde25e8218cab2885dd81ffe3b888d608a396dfcb78d75ba03f3f +b1479e7aa34594ec8a45a97611d377206597149ece991a8cef1399738e99c3fa124a40396a356ab2ea135550a9f6a89f +b75570fc94b491aef11f70ef82aeb00b351c17d216770f9f3bd87f3b5ac90893d70f319b8e0d2450dc8e21b57e26df94 +a5e3f3ab112530fe5c3b41167f7db5708e65479b765b941ce137d647adb4f03781f7821bb4de80c5dc282c6d2680a13d +b9b9c81b4cac7aca7e7c7baac2369d763dd9846c9821536d7467b1a7ec2e2a87b22637ab8bbeddb61879a64d111aa345 +b1e3ee2c4dd03a60b2991d116c372de18f18fe279f712829b61c904103a2bd66202083925bc816d07884982e52a03212 +a13f0593791dbbd360b4f34af42d5cc275816a8db4b82503fe7c2ff6acc22ae4bd9581a1c8c236f682d5c4c02cc274cc +86ba8238d3ed490abcc3f9ecc541305876315fb71bca8aaf87538012daab019992753bf1e10f8670e33bff0d36db0bf0 +b65fbb89fafb0e2a66fe547a60246d00b98fe2cb65db4922d9cef6668de7b2f4bb6c25970f1e112df06b4d1d953d3f34 +abb2d413e6f9e3c5f582e6020f879104473a829380b96a28123eb2bdd41a7a195f769b6ac70b35ba52a9fee9d6a289c3 +88ec764573e501c9d69098a11ea1ad20cdc171362f76eb215129cfcca43460140741ea06cee65a1f21b708afb6f9d5b0 +a7aaec27246a3337911b0201f4c5b746e45780598004dac15d9d15e5682b4c688158adffdef7179abb654f686e4c6adc +a1128589258f1fbfa33341604c3cb07f2a30c651086f90dce63ae48b4f01782e27c3829de5102f847cde140374567c58 +aaf2b149c1ca9352c94cc201125452b1ed7ca7c361ed022d626899426cb2d4cc915d76c58fa58b3ad4a6284a9ae1bc45 +aaf5c71b18b27cd8fe1a9028027f2293f0753d400481655c0d88b081f150d0292fb9bd3e6acabb343a6afb4afdb103b5 +947c0257d1fb29ecc26c4dc5eab977ebb47d698b48f9357ce8ff2d2ed461c5725228cc354a285d2331a60d20de09ff67 +b73e996fa30f581699052ed06054c474ebdf3ae662c4dc6f889e827b8b6263df67aeff7f2c7f2919df319a99bdfdceb1 +b696355d3f742dd1bf5f6fbb8eee234e74653131278861bf5a76db85768f0988a73084e1ae03c2100644a1fa86a49688 +b0abca296a8898ac5897f61c50402bd96b59a7932de61b6e3c073d880d39fc8e109998c9dba666b774415edddcff1997 +b7abe07643a82a7cb409ee4177616e4f91ec1cf733699bf24dec90da0617fe3b52622edec6e12f54897c4b288278e4f3 +8a3fae76993edbc81d7b47f049279f4dd5c408133436605d934dee0eadde187d03e6483409713db122a2a412cd631647 +82eb8e48becfdf06b2d1b93bf072c35df210cf64ed6086267033ad219bf130c55ee60718f28a0e1cad7bc0a39d940260 +a88f783e32944a82ea1ea4206e52c4bcf9962b4232e3c3b45bd72932ee1082527bf80864ce82497e5a8e40f2a60962d0 +830cf6b1e99430ae93a3f26fbfb92c741c895b017924dcd9e418c3dc4a5b21105850a8dd2536fa052667e508b90738f2 +990dce4c2c6f44bb6870328fba6aa2a26b0b8b2d57bfb24acf398b1edc0f3790665275f650884bd438d5403973469fa2 +a2e5b6232d81c94bcb7fed782e2d00ff70fc86a3abddbe4332cb0544b4e109ae9639a180ae4c1f416752ed668d918420 +b4cdf7c2b3753c8d96d92eb3d5fa984fef5d346a76dc5016552069e3f110356b82e9585b9c2f5313c76ffaecef3d6fd8 +83b23b87f91d8d602bff3a4aa1ead39fcc04b26cf113a9da6d2bd08ba7ea827f10b69a699c16911605b0126a9132140f +8aae7a2d9daa8a2b14f9168fe82933b35587a3e9ebf0f9c37bf1f8aa015f18fb116b7fba85a25c0b5e9f4b91ba1d350b +80d1163675145cc1fab9203d5581e4cd2bed26ad49f077a7927dec88814e0bed7912e6bbe6507613b8e393d5ee3be9be +93ddeb77b6a4c62f69b11cf36646ed089dcaa491590450456a525faf5659d810323b3effa0b908000887c20ac6b12c80 +9406360a2b105c44c45ba440055e40da5c41f64057e6b35a3786526869b853472e615e6beb957b62698a2e8a93608e13 +93bfc435ab9183d11e9ad17dac977a5b7e518db720e79a99072ce7e1b8fcb13a738806f414df5a3caa3e0b8a6ce38625 +8a12402c2509053500e8456d8b77470f1bbb9785dd7995ebbbe32fd7171406c7ce7bd89a96d0f41dbc6194e8f7442f42 +aab901e35bf17e6422722c52a9da8b7062d065169bf446ef0cbf8d68167a8b92dab57320c1470fee1f4fc6100269c6e2 +8cad277d9e2ba086378190d33f1116ba40071d2cb78d41012ec605c23f13009e187d094d785012b9c55038ec96324001 +85511c72e2894e75075436a163418279f660c417e1d7792edce5f95f2a52024d1b5677e2e150bf4339ad064f70420c60 +85549ca8dcbe49d16d4b3e2b8a30495f16c0de35711978ada1e2d88ad28e80872fca3fb02deb951b8bcb01b6555492e4 +8d379ab35194fe5edf98045a088db240a643509ddc2794c9900aa6b50535476daa92fd2b0a3d3d638c2069e535cd783b +b45cfebe529556b110392cb64059f4eb4d88aaf10f1000fdd986f7f140fdd878ce529c3c69dfd2c9d06f7b1e426e38f3 +ac009efd11f0c4cdd07dd4283a8181420a2ba6a4155b32c2fed6b9f913d98e057d0f5f85e6af82efc19eb4e2a97a82df +b2c2cdffa82f614e9cb5769b7c33c7d555e264e604e9b6138e19bcfc49284721180b0781ecbf321d7e60259174da9c3c +95789960f848797abbe1c66ef05d01d920228ca1f698130c7b1e6ca73bfda82cee672d30a9787688620554e8886554ee +98444018fa01b7273d3370eeb01adc8db902d5a69b9afc0aa9eadfeb43c4356863f19078d3c0d74e80f06ecf5a5223f4 +87d20b058050542f497c6645de59b8310f6eeec53acbc084e38b85414c3ea3016da3da690853498bde1c14de1db6f391 +a5c12b3a40e54bee82a315c503c1ce431309a862458030dde02376745ec1d6b9c1dbeea481ae6883425e9dae608e444e +b9daa3bf33f0a2979785067dcece83250e7bf6deb75bb1dbbab4af9e95ddfb3d38c288cbef3f80519a8916a77a43b56c +b682ec3118f71bde6c08f06ea53378ea404f8a1c4c273dd08989f2df39d6634f6463be1d172ac0e06f0fa19ac4a62366 +a4f94fd51ecf9d2065177593970854d3dce745eebb2a6d49c573cbf64a586ae949ddfa60466aaef0c0afb22bd92e0b57 +86cd5609efd570c51adbc606c1c63759c5f4f025fcbefab6bc3045b6ad2423628c68f5931ff56fdda985168ce993cc24 +981192e31e62e45572f933e86cdd5b1d28b1790b255c491c79bd9bb4964359b0e5f94f2ae0e00ef7fe7891b5c3904932 +9898f52b57472ebc7053f7bf7ab6695ce8df6213fc7f2d6f6ea68b5baad86ec1371a29304cae1baadf15083296958d27 +b676c4a8a791ae00a2405a0c88b9544878749a7235d3a5a9f53a3f822e0c5c1b147a7f3f0fc228049dc46e87aa6b6368 +9976e10beff544e5c1645c81a807739eff90449df58ffdd8d1aa45dd50b4c62f9370538b9855a00dd596480f38ebe7a5 +a0e91404894187ec23c16d39d647ada912a2c4febfd050a1ea433c4bfdc1568b4e97a78a89ba643aca3e2782033c3c58 +91a6ea9a80476ed137eb81558ff1d55b8581663cccd41db4fc286876226b6515fd38661557419e1e46b6a3bc9cda3741 +b9e8a1e23c60335a37a16f8085f80178a17d5e055d87ffe8cf63c532af923e5a5a2d76cf078164fb577996683796caa6 +ad8e151d87a37e8df438d0a6a7c02c3f511143efb93fde8aef334d218cb25932baf9e97c2f36c633620a024a5626af3d +978f942f210e8a482015e6fdc35a4c967c67b66e6e2a17a05cc7a0f2163aed227b775d4352b0c3cca6cbf4bd5bafaf75 +b5e2e3d8b2e871c07f5899e108e133f87479959b80cb8a103fbecde00ccdbfbd997540eef33079c5cc14b1c00c009fd1 +88a164b3fefd36857f429ab10002243b053f5d386466dbb9e5135ed3c72dd369a5a25e5e2aaa11f25488535e044e2f12 +a66091c0db4e7cf05a089ec2b9ff74744354d0196968201f5e201699144b52bb13b4e68e12502727163e6db96e3565f2 +8e65aff8e37240461b7374c20bfd1d58b73a525c28994a98f723daed9486130b3189f8efe5c5efcd7f5390cc366038da +8b37c21dd7304c3aa366959ba8c77ea8b22164a67e136808b6f8e48604297f7429a6c6ecf67b1d09b8b7ec083eacd7e0 +b689b1277ad050f53da91a702516a06d7406ff33a4714ea859b3b2b69f8d0aa8f983c7e039b19c0759a3815d841fa409 +b17f7a0a182ed4937f88489e4c4e6163dcf49fd2ea4d9efbba8126c743bea951cd769752acd02e921774dc8ebcfae33b +8b7fab4f90be825ac5d782a438e55c0a86be1c314a5dbc3cc6ed60760a8a94ef296391f1f6363652200cce4c188dae67 +ab8410c4eaa2bb43b0dd271aa2836061bc95cb600b0be331dada76ddb46711ff7a4ad8c466cc1078b9f9131f0dc9d879 +9194bd7b3cc218624459d51c4d6dbc13da5d3de313448f8175650fa4cfab7cc4afcda5427b6676c3c13897dc638b401e +980f61a0f01349acd8fc9fdc88fc2c5813610c07eecb6ab14af0845a980792a60dadf13bb4437b0169ae3eff8f5984ce +b783bee24acea9c99d16434195c6940cf01fc2db135e21f16acae45a509eca3af6b9232a8aa3a86f9715c5f6a85cb1c3 +a3079931c4b90966d1faa948db847741878b5828bc60325f5ebe554dcab4adcc19ee8bce645e48a8f4a9413bb3c6a093 +801f61ac9318f6e033a99071a46ae06ed249394638c19720831fff850226363a4ae8486dd00967746298ee9f1d65462f +b34dbbed4f3bb91f28285c40f64ce60c691737cc2b2d2be5c7d0210611cd58341bb5bda51bb642d3ee2d80882e642a13 +8750af19abfb915e63c81542b13d84526a0c809179bbcc1cd8a52b29f3aba3ae0f7cf6f4f01790bf64ef7db01d8ee887 +a6ea10000eb2dd4efc242ac95bc3b3873cdd882fbeb7c9538c87e3143a263ca3a2e192b2159316a625cfb5fb0b6cdcb3 +aa40ca54bc758a6c64cb932924917581062e088b3ad43976b28f2e11d8a7dea73f1fb50aeaa0e70182bb2dc07d805bb9 +a4779dfd25b5ec9d75dfb54a4bb030364899a5e75c1492403acb19f2adc782c7ac4daeb66d2f5aeb74135afe9f318e3f +b4551e2805d63ca453f4f38b1921ac87ff687e1d70575ad38f3469d6f0608ef76b7b1b98ae1e6b1e7d928773aaab6e3b +99490ee722f96aad2743b08dd37bfeb75a8c59efaee4c9b694eaa05eb8a6bb23861a4480544c7617d04d23fd5e2543b4 +8a7050d964d295fff98ae30d77ce730a055719313457e773fcce94c4d71a9b7cf63db67e54a8aab20fb1335b0130b5d5 +903144e6bbee0a4fec17ff80fef0d2103981140c3d41776cfb184ced17f480a687dd093f6b538584327e6142812e3cd5 +a5b30f7c6939bdc24a84ae784add927fec798b5a5ee3dd156c652df020728dd6d43898be364cf5ee181725fbcffc0964 +b43d97ec2bc66af92d921a5c5c20a03ef2be2bc2c9b345f46d8287409fcbfd88ebc49d4509d64468222cd1d2021bf236 +82dc23c7f5086c9ac6b4566359bfb830d203544b0d8332a210775670f899cd9ff48b94bfeba40040c25664ebdd5cfad8 +9294cd017fea581dabb73dcc8c619904d7e022b664b0a8502c9d30f3807668af279948e7e41030ae296d492225297e95 +8d6c9dc636c8e884f9a4299e5cff06d044ebc94ad783a4b71788347ea4a336d4d048b8a9ecabae789e8fcdc459723dfb +801a80bc49e882ec81b04e37407713f033f7bdac79252dfa3dc8c5bd0229fcbd4019890e402cf843b9378df08f72ab84 +b4313ca32569d973900f6196363c0b280ddfa1b47c88d019e5f399b805b444a777950fc21ae198fc23ece52674b94abf +96f06056fd255fdabf78986e315e7c4fdf5495cf850536b7976baa97a994cc6a99c34609c33a0f2facba5e6f1026dce6 +983ed80220a5545ffd70ef5e6ac10217d82ec9cd8f9a27ee77a5ff4074092308c0e6396fc4e9932a77ddd474e61f8b55 +872a059aa630af73c4abbd076e8b333a973ffc5bdecf5dcc0600b00162184213cb19d4f601795030033beb808d5810ce +b040f318d9d3b8833da854014a44296dbd6762dd17cab13f91987256c54353b7f0800547cb645a7cc231997454209fdd +a8c4731a555308e8ce0b8325eb7a4cbf6113d07e9f41932df04480b72628d313b941c7055f1cc2ac45c7353b56e96ca9 +8c24031440b77637e045a52e5ea3f488926ab0b426148975edf066c40a4581beecc1bfb18fc4cf5f9f96dc6681b4bd28 +b39254b475abf342f301298feaa17a4b3051f30ea23a18acf59e003e2704ac96fe40691f1da387913bdf7aee6389f9a8 +a1dbf938b604ccc6d60881cc71f38df568aa02752aa44d123514154017503f6c1c335ae43e359f1487bc8934073cd9c1 +8d52aa1be9f429ece0580498d8fe9fef46d4a11f49436a82b8927f9503dacc41245907f126594c1cd30701286f8c092c +b826f396486942c0326d16f30a01b00a682c30a75553dc6ac34fd5b3e96b13c33b94738f522eebaffb59ff8c571c76e9 +aa89f51cbf6e6c3e2aa2806187b69ab3361c84e89f393f3ed284fe84db46fc3944aa44f8928e3964f9c1a1ec27048f68 +a254df0efa4203fb92b42a1cd81ca955922e14bf408262c8f7cb7dc703da0ca2c71556bd2d05b22ce9a90ad77309833d +93263c507e4d5f4e5df88e85b3d85c46ea729fb542a718b196333e2d9fb8a2e62dc1347cf146466a54ba12d200ef09d9 +922e3c4a84246d89a07aa3e90f02e04b2cea9bebc0e68b742156f702aed31b28c6dfa7ac936ea2fc2e029adf68361f98 +9a00628eeeda4ccbed3ef7834149aec4c77aac1a14bc2491ba5d1a4a2c5d29afb82ceaa5aac1c5ce1e42cdcaf53e30ba +ab3a88df36d703920f6648a295a70ffa5316c96044f39ff132937bfda768937cb6a479e9ba4a4e66b377f3a9996a88c4 +966b11526ab099d550ab33c6a9667e5cfdedf255da17a80a519d09acd78d2ea24ec18bd1ea7d8d63cf0a408f1c1fe0b3 +b5c21b9817dc32f3df9d9988aa3560e1e840d586d01cd596bc0f850ab416b6013cbf7dbfd05ac981f26014c74bd2d2b2 +9040abef5e2523e7f139c9f744a64b98fea3a57952059ffe4d5ed77fa87068203c090ef4e7f52c88fb82ea8a6fdca33e +a0dcdaeb7d3f5d30d49c004c5f478818c470187f4b0b4856812dcd1b3a86de58a99acb8ceb44c6b80c3060cf967c43a4 +b5f4be9a69e4a6719ea91104820df8623b6d1073e8ee4168de10a7e49c8babea772bcbc6b0908185e98d607e49cd3609 +8634020a5a78650015763c06121c606d2dd7b324aa17387910513dd6480fb797df541fc15b70d269b2794ad190595084 +9504d1d0fb31ff1926c89040c04d51fd1f5cddf9d7ca3d036e7fd17e7a0f767ef33cee1d8bf7e17e2bc40949e7630417 +812c72846ef6d692cf11d8f8c3de8fa78cc287303315114492667b19c702cd24d462020f1276895df26e937c38f361f8 +8c97aa5e9ef2aa9a1435ef9ddfe62e850f0360864ed5fb82bf9fef4ef04d8fb4f827dc078bc911ee275e4501edd6617c +ac5f7af5e23c8e429aaa6b6825129922b59d25b4608f07b65f21388a9ac3aa89096712f320afe6d56e44e1f0d51a4eb9 +a8c84d9a8593a0cb5be1e450960f59878a4e6b70da54a7613dfc25911b7cc9e6d789d39401b0a0d6471ab9dcdc707976 +8c9d5fd89611392c0f085ffa4fa642a181f0b9b23593deb5e10fdd1642722ca75ef34a037e88a8d03f2888fe7461f27c +8c74b05f91fb95c85e7bd41f6d9a1e41e667e68f3d19b325c1f25df1767019919edab89b92af237896cbc4e6d6dc1854 +a3caecb91640821f0b2c4981b23f2069df8d2b98ce026c1538bc096b292f5f956a5d52c1c8d6a8165a1608083ba6494b +8ae8e0c36f8b79a69176ff29855df45d0fcd9e4d1dbaed8899f8fcdece676e418ec034a6c161e2a894f0c834aaecbfd1 +b88d18c67dc3b1b6ed60ee437c441c1ed14ecddebccf43683605716f30058b1aa4ba05ff10cd8171ee97d8f58d70c094 +94f43d84dcdfd9cd19115c7d8e9c1e856828eafbfdec93b876cf0007e317e30b2ad951dbabc186aa6ef90fdee4d91990 +b44e4723f41fc1d5b0057f371e3381ae02566590b3f964b6eb07b2104f66ff78410c407235fa98d04f635694f3baca09 +addd8390173d29ca0811534d389253831fed75fed135398617836b6e70767269eacb1560b39a58f02042ca3b97fe59c4 +80bdbdacc0c358c7ea52aeacdc5f9ceb6928bcf6e7dee7c17d8ae3bf7c2372aa7a0372363888968fc0921aaf4776d5d0 +a486e2b6f04f403f9e609d69dfb3cfb992af56ecad1683271df3e3faa3b86638b81e73b39978fb829ee7133d72901f2d +a19472da57457e10c6a6307895393ddaec8f523760d66937fe26a025817319e234eaf69756ffdf1b84c81733424a96d7 +ad6a195397cbc2d75171f5e82090441eed60bd1ba42c39ef565b8b5a8281b04400678625b1dc46d617f694a7652a8e5d +8f98e721c06cec432e2221f2e1b06bb1469d916a8d88d6973acf68d1e003441d00390dafcead8ecdbf9eae4509baf5aa +91d62a0f9d13c59adfe1376ed6d057eae244d13c6b3d99be49a49e0075cf20f4085cf127774644ac93615be9ac9e5db6 +af45dec199245e2b326a0d79c4899ed44b1c0219db42602a4a6184ace0ff831a3276297af28f92e8b008ba412318e33e +8754bde54e8d2d169e6a7d6f0eae6097bc0461c395192bd00dd6f105677ea56ab384c02553ea5eeac0a65adcb0df77ee +b676afd2f5afc37a314c943d496e31b4885efcbcc2061036e370a74cfde5642bb035622d78d693bfc3136fc036c7edb4 +aab6ffe6cc234397cf1822e02912bc282dfb314e92fb5a9e10d0c34ee9b5856d4b76e166bc2bb6fcdd66aabea35ec4ef +ada6e62f90ee6b852ec4b72b22367acac2896f0df2c105beda27096583ddbedddc710d171330569f111c6e44a5b57ae7 +802139dd15241a6de663d9b810121bdd9cf11f7f8c8ca6de63f4f8e731409e40d1fd3558b4f619ed42ee54929dff1c7e +ad8e70531cec21b4e6f55be1751c2d025bd2d7d8158269b054cfe57fa29252d052ce4478ec7db6ec705789e2118d63b3 +a8e4a4271769480e1b33a28c87a150ecc0b48bfe8a15ae04152197881de4ce4b03453aefe574842424edbbe4173e1a3a +b98c65726296610cef16c5b58da5491acd33bd5c5c5af4d934a9840649ef85730fbce8018dee09ded14e278009ed094a +8e213a7861223287b860f040e5caaa563daa0b681e4e09ec79ad00cc459238e70bbeaf7486bbe182fc12650700034ec5 +a2879f9e1a556cf89b9b5b3bd8646a8cce6b60bcbc8095df44637f66a2da5858eee2dc9091475a8f64bb5aff849389cd +8a17cdb4077b9b0bcf28b93294ac5ae4c8bba8839fce0f1012b53187ac008f9858b02925fbfc421f1123afcdbd8b7753 +86fd9c11528aa43946e4415ff64a3ca6409ee6f807368c68997b18605da65e415ccd85ad913820d450cb386593de666d +8ed55923b963c3d85a91aca11c40ff9c6c7f1e2b9bc199d1a270e5fb16aa62dec0136e97866145ae9d58a493e8b1cbbb +ae32af5b5d418668ae123c639b149e5eed602404e8516da4a61db944b537a3620545e8e3d38cf10cdaea980ab2f80973 +95cb8d9e9d6762d78dde0ad73869ffaca904a7d763a378b8cc11a7933d3e7d1c8aec4271a079b1b00f8887ee5b1ea21f +b5ea20b42a3ca247f00ab5328c05f0cf194973d5f7271c66c41c5055b1ffdca136be179709e0c1de209fbe07b9820bf3 +98682f7cce471c92a8d6d15fee4ddf4d43dd97c3e3811d2913618ecacc6440b737717c07736ae4558c910e11ee98104e +a67da2c7cbba48e929ca4e4b9a6299fe01ef79eff8cc5cd3fdbdc0721a68130e4079f30ae151a573a7dcca8ecf2e684e +a9981c9f9dcbb3b0f6996f664fb2acd7573189f203be37b2b714662aa273551396abfb1f612ccde4e4c8127a050dbe4b +92d55eff8da600f886da9bf68e8eecf482faa4b268f3f286b3b3e5cc91b19604081498d4905b201bb4ec68e32b5591d9 +963e3f1728de9d719c86d390f3eb9c3f99d1928347fab0abf10dbb37d76b59ddb64d4734c977863a6cd03ffece5ca895 +93480e2de83c921056b6d8628ac37cd5ef7555ba43b0308fc13386cb0515d42c12ecd06057137aa71a7931beaf90b9ce +8feae57ff0e6a162cc81c99f45c6187d268fc0bee8c2bffc92142ef76c253d201f0e932943cf2fa312982b281ce1066b +8f8f4bd4200fb87afcd743274480220d77571928000d4197410dbb75439d368df6a06d941a6152206371d2ca9cac99e4 +8ee7f11e79af4478e0a70eb424fe8078237ad99ba6d7e6bf1a8d5e44e40abd22d404bd39b718ad6fdf4c6601f2a47665 +a98acfcec612b574943195b9ba95bebcc9c0b945c9f6b3e8760b2a4635909246a9d73b0b095c27b4ecb3339704e389b7 +b520efd19f65e81dc285031ea3593f8c5dad793e4426beb9196ab46e45346f265fd71e50adb0da657977c60ed5724128 +a3d9d0b7415280ce4dfa2429d47b2b8e37604a5157280a72cc81d541ffe44612dbb3ef7d03693fc42a569169d5842dc3 +8c29e2d0b33801f6d9a9c065a76c5cad1fb0a001506b970307e21765ee97c732a4cbf1d7c1b72d95e0ad340b3b075224 +839e21f292892a6eb596b9b1e9c4bd7c22a6fe71d3d04487c77840028d48392c5cbe73140a4e742338e0c8475cd0c1ad +8bea5c68e7743998619185bb662e958f1b4d3ca81019d84ac43c88911aab3abe4ee9bcc73cb95aa3ae87c0138801bde3 +b8f262d21a94604049e008ce03dc857848168e1efca4522acb0ccc827ffb37f545e1947843a356563a76bc6489605b66 +a7bd0842b0bb38d9943b82aa883f36f4eb8a6e8a7790d4f87faf306608f51d250a19b73984f1156cef5dd2581664614b +a993e649bd953627a88a2539dac3a12ec7f37a4c65b01425d9d34edf7ee10a71aa98f65c9e013107f824faf8aee041a9 +8e07eced75c67cb4d2ec01857f6ac1408482e6b31cb2faa249e8cf99f180575587df530c7782a7539b5221121ef48aa0 +b2f4578f26c05ecb9e2669ca744eb19d4f737321ac7d04fafd18beb7866e0fec9dd063953ae1f077b44b9c6f54db1279 +b6b3788a6c7bcaf467d19daf6ab884d549aa866970c05a9181f544ff190d043192c84fe437a75a30b78b425461cca062 +a270684903c61544b85a7041e81f65e787e1c1e23e57538fa8a69836bed0ca1673861dd29f743a1280f2f38eddd3aa83 +a9c2397c4773dcad2821266dadfd2401d013d9f35de6744f2ec201f3507700adb1e6ec4f5a453be4764da8bf68543f26 +83a3025ed6fd5df9d98be32a74e10a0d9728b560942d33ba028536fb148fc34ae87e92be2df3e420a8dfec08da495982 +90dc70c183a90bab988b4a85b7b921c8070af0e5f220364fe11afa0722990b2c971e1e98eef62d3287fedfd9411f1df7 +82d940937a6c636224d04f8e2536f93dcf20dc97a5f188875ad76c21b804aef9af10839419b61143c1f88a695959a6b4 +8017f9473ce49d498d6f168137e77e62fe553e5a51e75b519cf2cbd1ab9afdafad80fd5e6fd0860e640b0d78ca8ed947 +80573a0ec049fe1f7b3013b2839e145cd87e07c0e43826a29ef8c92516f9a30896c2ffcf3ed77ed22a6cf3101b1789d5 +953349abd2559f9824db07cec857ad54f1a05018f3076425f8dbae37f8d92a46af2c04ab7c8ec0250449541187696e98 +ab7bd2c4f05ee9a9f252c4e16a20993a12c535c3809d124bae24642616521a9768d3f19eceaf8524583f47ae1f527684 +9883b77ee834ee0112ca2f366d2a6fc213e0cf454e061438c2901a5ba35b7378f64da8adf6a476eb1562991ef5b4a5bc +89291811db308637356dbf7ed22cf07bfce33eb977734ee346e8c15a231b35d8b4443574f3fa97a40867b3e23b0bbfa4 +93d753849d7d9588d39e38217500b123a6b628a873876612d9f98b5d611f52c89c573432d2176752b5d1cc2d94899b8b +a45add3c4844db3b7a237295fc85fddc788ac1ec395a0524d2fc90a539571a247146aea4aa10eec30a95e9617c85b98d +90f94578842db7a4de672da1e483858ece5e466c73c12f725a0fc71f42ff880c9447a33fa9096839bee817536f2591e2 +b2c1b6fb031bb30460f157356562b44b4de096a0a112eab4fb3cc500aad38bc770da1fc2e73caf687a0da5e8537049c0 +afb15e15fd930929c0e3c66482068a5afe0c7b7f82e216a76c5eb1113625bfa0b045a52259d472284cfbaf4796c71456 +ad222a9a3d907713418c151b8793d5e37634354322068f8206b9d0da1a3f53b0004193713d23ec35990639a1b6c2e075 +b44a128dce97e8c4b178cdbca0a5c1b3f6e164490fac0fd68dbfe0aafa89920bb4ea420a8527e06c80dd19c2f135e3ef +8596e993ef18b8d94e9c42a90cb7060affc586b8e9b526820d25124285de5590134e2e86592e9dc4dd45ccf5d578fa60 +b71bb0ad138141ed506b2253e84110d2db97cc2d24a3fd0d096b0022d9f38f87aa74e2f505074632d64e90bcc491aa30 +84841eafd357309de47b92ca5ec163dec094a2e5271bc65898c31932e0160bee165e4decb23af339cfe09c83e1cc5441 +8a2915ee39a6fd4a240b98533d7690ef1773ce578ed1fb05ed414ebe36f7ef289fa46f41768df57190438c356331e329 +90bb337165386f1990cbd8ed2e8321ef21bc18125b015b4da0c37e5fcc446b26005379ee4fad8ce9348ceb4ab49e82e2 +b707b50ea2ab05c6d183671587f25fe29eef23fe569d731459a1ac111a0b83a2cd65b88242876b34aeead3b05a15d745 +ae1f159f79b7996315c4f9acce7e21a6ed59d4ef76331196fc86911fda3035edd5c11d568b105175a36c948d0263b382 +922bc525bace05e5dff6b5cabde5469ddd2c1c601f7131abc04ecefdd35095e6ac015b1aec3c3b25c5dee8d139baf60d +a7b060405b2740f82db64683187b1bb89e5f40c8438663c7cbc8ef2513929fe5f92625667a7f2f599a72a96b1fc8f08a +b9dfe94a08651db5efefbb813269bce80d814e3089b80c0654491e438d820bf521f8a4a4477909344ba88f7683eebb43 +841817a9729465743576950b6e8eea32ebf39cca99ace86c4792f9f35926e2d6830c52854a3b2eaeb61694e6845008bd +934128034bde8fc7b93b952aa56e0ed28b36cfa04cfa1f0d5b38266dd40beedff5e0bab86e4717b0fb56c56be2eae26b +aee9d64caf28596308782cd8f3cf819506daf3378f86157ff775e618596411adf94efd0e9542787ca942066f02cbd332 +85871184db314411a49575fee088c52ed5dba4e916ee001ec24d90898a0154d9790a06aa8a707ca7a8b986c0293b8d89 +8d3d87edcc0187a099c97b581a598d357a41ac152303bb27c849eb78e72e15cb97cf9a0468fc36f245c3e152c76bb7dd +900475d165dec18b99eb7b5f9e9ad1d2d4f632e55fdcc4c5ecd7775fed462990e6aaafe9c669f40508f9b15f00bda31f +a25b5954edd57e7811a0d18532043d975c7b44b80f65cd630935d7b16ada05f30fe2b7be7ae8a2f54c25957faf3f1950 +a089019afa3a7a15f7e7874e73b6773c0a824e6d3379b4c928e173321fb165ad979a6be004d394c28d19d410b2655d3e +b28f46797dee0c538bd3de815df641a0ef718ad3e52b2764aec380d6905b38b50ad6f60d0f68e096ca39960ba7734355 +b0ac155d3d05851b04104e6b459f1a68e9e155437c92421a7c0e4dd511ef89cf71dfa3cc920769492ee283a65ebf029e +813c69a810745580d43d5b5480f0ba81000fbef0071e6b655c7346bef5ed774e9214a7816d40eb1774a5bd033767a046 +b176345ca75c64f10ec33daa0dcf1f282b66a862fcd3d8d66c913f9a02db4c9d283dadc02eff13aaab94bc932a42234e +92560f67e5b995db4a489bb86ee78b4aee0800143b3535ad557a53e9e08716bd0202d9f5714722c2a5e8310046e3f5b3 +8adb427bad9cc15fc6c457a96a6750dda8c46d859c5f69bf0e7ab8fc0964430b33967fd47cf0675b6ba1757f91255e6e +b120f723b80389a025b2daa891b140b3d7b8d520ae2a6a313f6e3d365a217af73292dcb249dca1f414ec05e865e3cdc7 +a61a5d261a8dfe5996c42ea0a5ae703a2adcfda80e86837074d868eee16f87d38da19596c48b55dbd7a7cbec1a9b4996 +99dc921eacc6bb867c5825ad4c83bc4af9dd78a18b3d0e1a60ad493e3805b8fb9b7922b577da1adb3d805edfc128d51d +85455fa165a07282aaab4a5bfb88027f47b9532e4af8195c048515f88b0db7e80f42e7a385fd4944faaa7f2a6544ad17 +96dff2d1c8a879d443fe576d46bcceaf5f4551d2e8aad9c1a30883637c91090de99ad5eec228eb5febf93911502d3cbb +a87eb7f439377fb26c6bfe779701f4aea78dd7980b452a386afec62905e75217a1996c5234853432a62ef8bab21c31c3 +b598278293823e9ccb638232a799211173b906444376337fdf044d0227d28fcc4c5867e6ecb3200e59ca0b139e71cac9 +aa6fe147edc95027654d68140f428ec53cede3552c5f49c09d18bc6f6ae8c739a63042eb7291d14d717a4e1f0778abcb +ae8ee18913d328b2fba71efe65526d3ee9c81beda53cf776baec4019ea30212010758cbb5dc85ed6620ce04b189f01f2 +ae9fb686777e88dffdd42805fe4114aa0da1b350d92a27ff3f8a817fb25af1fcfc9a06155affe0273bf13caad16a5351 +95d372ba3a2ee38371538f34aae91b4844488e273f70c02f1992370f89fc2343eff95692d52ce9f21206abbee4959958 +b15260376f0a34ca2827ff53acd7eaaef94c9acc2f244b36500423069cb1cdaa57ac8dd74adb5b53d0fd4265fcbb28ea +b0ffce6a8059537ef6affdbbc300547ef86e00109289239b0c6930456c562b4ed97f2e523963af17736dd71b46c44ac7 +b5499a1277d34f9892f7579731ff53f423f2ffffa9ea43a6e929df8c525e301396249a2324818a6a03daa0e71fcd47b3 +98dbfb8e97a377a25605a7665d4d53e66146204d8953afda661ae506858c5cd77ff7f21f5f10232e06dbc37378638948 +84177e27e6da0e900c51f17077f5991e0e61bff00ca62c1623e627c5aea1b743f86eef6d55b13219a1947515150bade6 +b50407bb5c61b057ab8935df94fd43ca04870015705b4f30ceac85c1035db0eb8293babc3d40e513b6fb6792ecbc27a9 +988699a16917514e37f41ab5c24f4835ed8a2ca85d99972646fcc47c7e2a83c2816011144a8968a119657c4cda78d517 +920c43fdcb738239ad542cb6504ab34498bce892311c781971d7db4dec70e288676de4d8697024b108cfa8757fa74035 +aaa106329aac882e8d46b523f126a86d3cee2d888035ce65c0be4eaae3e92fd862f6ac2da458a835539cccafaba9e626 +96e4c1562d14b7556f3d3e8a1b34ea4addc5a8170e1df541dc344728bcb74cd1630eb7ba4c70e9c68fd23c5c5d5a729b +a616ac5016d4e68e03074273cd3df9693ee0ce3458e8758b117a5c1bc6306dd2c7fad96b1bb37219c57ac62c78ad7a3e +8db7d9b20abfb1445babd484ae9e38ff9153ac8492230d7591e14e3fca7388a5ca6ef7d92ed445c8943cf5263e4a6ad7 +88464134221aa7134878eb10928f31c8bd752ab68c27c9061c1de3f145c85731a4b76acdc7e939b399b6e497f9e6c136 +a5f7c794f70b7c191c835dded21d442b6514bab5e4d19b56f630b6a2f1a84a1d69102d7a0dcca256aab5882d3f30f3ca +b96b6f98b6817b5fa6b1b1044e2411bdf08bf3ffaa9f38915d59e1d2b9bed8b3d645eee322ee611102ce308be19dbc15 +92c26ade2e57257f498ac4ff0672d60b7ea26dad3eb39ed9a265162ccd205c36b882dba3689758c675f29e20836b62d9 +8379a0299e75774930577071d258e89e471951642b98e5e664c148af584d80df4caa4bd370174dae258848c306f44be5 +a0e53beda02bd82bf3d24bd1b65b656238128e734b6c7a65e3e45d3658d934f909c86ca4c3f2d19e0ac3c7aae58b342e +8ca5ceaeaf139188afd48f9bf034d8baf77bbf9669791c7e56ebf783394d7fcdf2a25fa4bdfcddfde649aa0dc67ccccd +a8060e6448844e9db4e9fb4da1c04bcf88fda4542def5d223f62c161490cf1408a85b7c484341929c0f9ce2a1d63e84b +af6e1a5ecf50b754bb9eb2723096c9e9a8e82c29e9dcaa8856ab70074430534c5395534e1c0ed9ce98f4b84d4082fa67 +81c8dbbef98f1b561e531683d5ae0f9b27b7f45dc6b2f6d61119ca0d559bf4ceb676d320afc5aba1811eeef7547a59d8 +85b46cd64d605c7090a2faf1a2aadf22403b3692b3de1d83e38b2de0108d90ac56be35b0dca92c7a41c4b179a3567268 +8dd3cc3062ddbe17fd962c2452c2968c73739608f007ad81fa1788931c0e0dda65032f344a12249d743852eb1a6d52a9 +8630f1707aea9c90937b915f1f3d9d7ba6bda6d7fdef7a40877a40c1ee52471fd888f84c2b2c30b125451b2834f90d3b +b4a747e0bd4e1e0357861184dacec6714b2b7e4ee52fa227724369334cf54861d2f61724a4666dae249aa967d8e3972f +a72de682e6f9490b808d58f34a0d67f25db393c6941f9342a375de9ca560e4c5825c83797d7df6ed812b71a25e582fff +8d5ea7d5c01f1f41fffe282a334262cc4c31b5dcf31f42cc31d6c8e37c9bd2f1620a45519dab71e108fe21211c275b6c +8ccdc7e3642c2894acbf9367f3e99c85963cea46dc5473d175339a2391be57dd8815feacadec766e13645971213b9eb8 +858e9b5fc8c13b651ff8eb92324bdda281db4cf39f7e7bd0472908b3e50b761fa06687f3d46f4047643029dc3e0ceeaa +ae20d36c70cd754128c07cbc18dcb8d58b17d7e83416e84964b71ccff9701f63d93b2b44ec3fddc13bbe42ebdd66221e +860dbf7013da7709e24b491de198cb2fa2ffd49a392a7714ad2ab69a656ca23f6eafa90d6fdc2aa04a70f2c056af2703 +8f809e5119429840cb464ed0a1428762ba5e177a16c92581679d7a63f59e510fdc651c6cc84d11e3f663834fcafeafdd +8d8a8dce82c3c8ea7d1cb771865c618d1e3da2348e5d216c4cbbd0ac541107e19b8f8c826220ca631d6f0a329215a8d6 +86e3115c895ae965b819e9161511540445e887815502562930cedc040b162ecb1e8bdc1b6705f74d52bf3e927bc6b057 +b9833b81a14115865ca48c9c6a3855f985228e04cbc285f59bf163dca5e966d69579ea4dba530b1e53f20bd4dccdc919 +a71f5801838a6dbb162aa6f0be7beea56fadac1a4bcd8113a0a74ab14fc470a03775908c76822d64eb52a79b35530c05 +a77ab73ae94b6d3378884f57eee400eff4a2969aa26e76281f577a61257347de704794761ea1465dd22a6cc6304fbc4a +acd1c5df3c487c04cf27f002e81f2348a0119349b3691012526a7b0d3bf911cdd3accbc9883112ed2ba852145e57fe68 +8a28515a48832ac9eaf8a3fb3ad0829c46c944b4cb28acbcdbca1d0d4c3c623a36cda53a29291b8f2e0ea8ee056b1dee +846bafca11a7f45b674237359b2966b7bf5161916a18cf69f3ec42c855792d967d3bf3f3799b72d008766206bb7a1aa3 +b24b341675b1db9a72c3405bbe4a95ccdfd18fa96f876ec946ccb5108f73e8816019998218a036b005ef9a458e75aeb3 +b99c267b4a09193f3448bc8c323e91ef5b97e23aeff227033fe5f00e19bab5583f6e5fcb472ec84f12b13a54d5c0e286 +a088aa478dbe45973b04ecafbcbd7ee85c9a77f594046545cdb83697a0c2b01b22b1af0b97dd75d387bb889e17f17aa7 +a0c6b0cdff2d69964134a014e36c3709d9e63f6463c5cd7b01b6f0be673731b202d577539d89dd57a888326da1df95af +b4e6dc4ef11b2b41794ece70a8968e56705199d183366759568b6fa845d2cae127486e926b5b27ae9118bb21d1682c1d +a007804353f174098f02540a57e96227232444d5ae0a24232c244647148b6c049848cbd2b50d0a25af3ca9164bfff8ee +873fb034cc39c9cee553ece908fbf315f62efbc412b9afdde6a1889326b7f6f813e050b0601ba9921688e958cb75942e +b5676c90f0106c40d8683299e59d564f505ec990230cb076caef3ae33f2021e6aa5c9b27bb8fead05fc076df034c28f5 +b5a67fc4c5539ad1ddf946a063110f824f7f08d2e4d30762c9d437748c96c9147a88efc22260573803ab545c18b108f2 +817ff2b748a949973a91b69b0ec38efbd945aeb26a176d19f0fb76e261c7526c759e6f5516f9ed34de6eb1ac7838c9cb +99b76bda3526a5d841e059010fdb14eb2fa035a7d10463373a062a98c3c1a123e2da0848421dd7546d776438fd05e304 +aa0d363270f90d56bbee7ea577b0c358532bda36d9247af6c57d000044a97ba41e35bb0db438f4c94551c6350e4e0674 +acdae205d05f54b9544be96c9032350511895ccf413dbbc56d1f03053185df22a6d5b7ffcc3fbe96c3e2ce898ccfa73e +b091c220a1de18d384f50dd071dca4648ca4e708162c52a60e2cedc0188e77c54639f75bce9a468a64b2549119c07ded +878676133e5c700b1d4844564fa92a9930badb5293d882aa25ee6721a9f2cfab02088c31d62cf1342ae3edaea99a1ea0 +9756d0793e6aba3b4dff48100bb49a5ec08ec733f966cb438379b91caf52fc2a5930830ec3f49aa15a02c82c1914dc7a +9722f760184d3b2d67cb2cea7fa41b1ff920a63446006bd98c6347c03d224d2d8328fa20ccd057690093d284b9a80360 +b5a68489de4f253715a67f0879437bfe8f4dfc4e655ca344848980e6153b1d728acde028bb66fd626fa72eedd46ff683 +a8cfc900b34835d9fd3add08044636f69614eff9ae929eac616c39bd760fd275ee89bf24b0f275dd77a66e54fd6b94e5 +89967479bebf70b2893cad993bf7236a9efe4042d4408022fdbb47788fabedcec27d3bba99db778fcde41e43887e45af +889235938fcec60275c2cf0f19d73a44d03877d817b60bb26f4cbce09db0afae86d42d6847b21f07b650af9b9381fa82 +b7fc321fa94557d8fbdd9fff55ab5c8788764614c1300d5ef1024290b2dbb9216bce15cb125da541f47b411a2e7e3c2d +b11b0c4dc9477176b3cda6b17858dbd8c35a933ed31364801093f310af082cb5a61700f36851e94835c5d4625bf89e32 +9874e54d2939ee0600f4194f183877c30da26d7515e9e268fea8d24a675dd2945d1565d9016b62b1baab875ac892f4d2 +90df3a77280d6f1fa25a986309bba9d5b89c3cf13656c933069bc78e6c314058716b62eacfa7ab4aff43518b8b815698 +962b08299a287d77f28d3609f39fd31bc0069f7d478de17539e61fcc517045050644b0307c917208b300ce5d32affcca +b30eedca41afb6f083442aaa00f2e4d5dc0fda58e66aaf0f44e93d4af5c4bf8ea22afec888cacbf3fae26d88e8d344cc +847747a22fab3fe3c8cd67f3f1d54440f0b34ce7b513225dc8eb4fa789d7d9f3577631c0890a3d251e782a78418fecfa +8d1ef3cb5836e4039b34ee4e1b4820128eb1e8540e350309e4b8fea80f3ae803d1f25f4b9c115482b324adf7c8178bc7 +8f8a2b0b0f24f09920b58c76f7d99ec2eb2e780b5a66f2f30a9ed267dcaea0ec63b472282076c7bf8548211376c72f6e +831ee6dc8889bbf4d345eaeb2f425959c112d2190764abbbe33bc44e1d9698af87ff5a54d01fac00cfee5878dee7c0f6 +a7eb2479ac80d0ee23f2648fd46c5e819ad3a1f4752b613607ae712961b300e37f98704880ac0a75f700f87d67853c7a +aa4d1b9cec62db549833000d51e83b930db21af1d37c250fdc15d97bc98de7a5af60dbf7268c8ec9c194d5d5ccda3c1d +87396fd7e78c4bcf270369c23bc533b7fb363ca50d67262937dab40c7f15bd8448a8ba42e93cf35fb8b22af76740d5e1 +a958b2a9ffccbca13c0c408f41afcfc14d3c7a4d30ea496ce786927399baaf3514ff70970ef4b2a72740105b8a304509 +a5963a9dd3fe5507e3453b3b8ed4b593a4d2ced75293aee21bfed7280283348d9e08bf8244c1fce459aa2470211d41ea +8b06ddc3359827558b2bb57caf78b3e5a319504f8047735fcc8ec0becf099c0104a60d4d86773e7b841eb5b6b3c0cc03 +9437e7278283f6d4d1a53d976c3c2c85c5fe9b5aec7e29d54a5423e425b4be15400ed314f72e22e7c44ee4bacf0e681c +b56067ee26a485ed532c16ec622bb09135a36c29b0451949aa36fee0b0954d4bf012e30d7e3fc56e9f153616b19349bc +a5c72f7f5d9f5b35e789830a064a59c10175093a0ce17654da7048827d0b9709b443a947346b0e5d96b5ea89b8d7c575 +a8318d01182d4c9af2847a29a6b947feef5795fc12e487a30001cc1ec482b48450c77af4837edfa1aedf69f0642c7e5e +82ea421c091552d3dafa7da161420cb5601b819e861dd2ba1a788c3d1b5e8fa75cc3f2b0db125dde8742eb45b335efa2 +8679fd1c7771ea3b12006d4a972f4f2892e61f108107d4586f58ee7f2533d95d89b9695d369cdace665f19c6bc3bc85e +b5ab3e8adee4c950fce4d33a0e2f85d3d886e60a6e2f4454b57bc68725f0cf246372d863167482cce1ea10a7c67c3af2 +a85696927075ec188979180326c689016a0dc7a2f14ae02ea27c39ef91418cd44177d3fca5752cf6b298fd75fa012e26 +a44f87b7232f102cd092f86c952a88afb635484a984da90a41a57a3d883c9469064bf105b9026024090486b6c6baa939 +866ac91a437db945bbfdc11fcee583f3669fa0a78a7cecf50fbfa6ed1026d63ad6125deba8291452bf0c04f2a50e5981 +b780d5a1e278fd4eef6139982e093ceafea16cb71d930768dea07c9689369ff589d0c7f47d5821d75fe93b28c5f41575 +b025d0046e643506e66642c2c6a5397a8117bbfe086cee4175ff8b7120e4f1e6794e1e3f6ec11390993cca26d207ae43 +a04a22b6e28c959ab265c7f48cde42bb6a00832c6beb2595b5df2879080a9424890960417d7d7ceb013d697d0ebf7267 +81de9c656ac27f54d60d0252e33aff4e9e9e9c3363a50740baf15a2b9061f730a51ae1704e8c4a626153cf66d47f19b1 +a15fab90599df889df11fa60c752948b68fba54005491180dafb66c5775547976d0eef33945e55d4818653e0818c6f92 +b06f9be44ddb103a72fa4ebc242c8ee1975fe9bf9ef7124afeda9967ff3db644dbf31440151b824869406851a90984a2 +99abdfe6806ae5efa2d11577da17bd874d847c5f810460148bc045bcf38c4fd564917eacb6ed61bb9164ed58055cd684 +ac53231077f83f0ae5f25e52b70bb6105d561c0ba178040c11c3df8450c508ed5df34f067fdaacf716f90b4926f36df5 +99e3f509af44fc8d4ebc693d3682db45fd282971659f142c1b9c61592573a008fc00502c6af296c59c2e3e43ed31ec7a +98f2f5819670aff9a344e1c401f9faf5db83f5c0953d3244cfa760762560e1c3a3c7692bb7107ea6eaf5247ac6fd7cc8 +b5b9f90391cec935db8d2b142571650fcbb6f6eb65b89c9329e84b10bfa1c656026674d70280ade4ba87eeaf9333714d +b0696b77ca8a0cdbe86cad12f358880926906fb50e14f55b1afc1e08478ae6376215cbb79bc9035de2808c7cd2b13b85 +a51d746833062a65fd458a48a390631d5d59e98e2230b80d8f852cfc57d77f05eefcfd3c395ade1e86d4a39c2141365c +812d67654319f4ef3c9e4a2d4f027a4cb7768f1ea3f5fdde8d1b79187a4b874ff9a5c70f15b7efa079c2dc69d1b9b1fe +968978b653c6416bf810f6c2ffa3d1abbefbd06f66b6686e9a4fdce3f869e0ab1e43cce14dc83786596761c100ae17e1 +98e1e6ab562ca7743783b802faeb0a24f1341abfb9655f106920aef08964a3c0e8083e1acda7ae28fed7cdd5478decb6 +a91c0b982a0a7085a103600edf99e9d0bee4c4e7db6d9f8f376c215c7d42476218462a3765f2928e12c3dd49d688e4fd +8a43395b3124fab9e2438635bf88952e8e3084dad7ecb3a9927f9af0e0887bce4707084043671fc98ad03621e40a149e +b0b37626143d4a8c6f5693d5f1fe871525b4dd946c4239cde032b91f60a4d7a930d7ba28959737550d71c4a870a3a3be +b01c74acae1715c19df08d5f4a10e0c19d1356264eb17938d97127bf57e09ced05ba30d0fc1a9f32d6cff8b0d5f91c9a +b4c2328eb8a5a673406faed8f0aebb8540d2791646e37ce46e0e382506570ca276eb6f8e166dbbf9e0a84064873473b9 +85cb9f769a185e3538e4a4beda9a008694e1bf8dfeea9dc07c5c40a9ceb1d31fcb13cacfaa52849ba1894b5027cb8c30 +8742f91cddc9a115ddc73982f980f750d82d3760f2d46ee4490d5b17c6c3bb57c7d4c7b8d6311b7b41e59464c009b6a5 +948ef86d17128a061e1bdd3ea7fcc7348e3ec87ec35dc20a58dd757d5d18037fe5e052bb359e27ab4c2320d9a52a6a0b +a70f6a214097c271e0d2d95e30fce72d38c30a2f186271fdff0e38e005aff5baed53739b8c4f9501aa7f529c5cb2da59 +892a7574cf6704ad75b346c95ae6f2668904f1218c35b89b07a0c2dbf3c62173c348f6fd9473926eef56a37c0f635c04 +837e85a41f39b4ded1420aa8fc3be46a7adb99305e0928c6d7643b7c44434b72984cea08eb68f5f803661df0db78c87d +94e495329f2aab3eeb68f347961d1006e69d990095877a4dcc376546233adf29a14bf6b16a0c39aa477e15368e87014c +851860a8fdf76a97048396553262637dade27f1f63f926997e74c7c72b14b10293eae7824e8dedffad1aead57c124f79 +90481017a250972055ab1cf45ff17d2469517f10f18c9d4ef79a9bdc97a49093289bbacfefa8a1e491bbb75388b34ac0 +983db15f7463df28091c691608ca9c51095530fa6b1b7b5b099c612e673d29e16787cc9ae1c64370ba6560582ce623c0 +a477dab41014c778a1b78a7ce5936b7b842124509424e3bfc02cc58878c841c45f9e04ccc58b4f2ff8231488fff0b627 +868ebba1c85d1f2a3bf34c0ab18721ea725378b24f6b6785637ee4019e65d4850e051c8408fe94a995cc918c7b193089 +93cbf4238a37ccd4c8654f01a96af809a7d5b81b9e1eab04be2f861d9d2470996fb67367e5bf9dcd602dc11a3e4cf185 +83113f4e696030cca9fdc2efc96ba179cf26887c677f76cde13820940ad6891cb106bb5b436d6b0f8867f2fd03933f7d +90c709f4e3359a6d215d03f45ad5cf8067aedd4aab03512dd62229696485a41dcd64e2acce327fda390e0352152fce13 +9945cfced107a36f3cf028ba04c653360afc5013858b9a12fac48802efcbc198c9baf3a7f9b23dfdd5036e88bc7274c8 +832ae60192b47fc735a8ddeaf68314b16256c90ab68099f58e43073e249c6939895c544a02fa34e40805bc6b5db33461 +8b12c335818b643c1d22cbc2869606cf64e7ae54a7713617fc4dd3b2f052ebd6b920ca59ba2e9c7aa8cf71bb4f40f9e8 +a2033eb7a373931c65d66989644aa0892ac3778b9a811b2f413d8bf534e282c339717979f9aa742162abb3468c195f87 +aba2b4c37dea36bed6d39323e5f628ab607699c66767f9bf24ef5df1bfcad00c2664123c0d8d5bd782f1e14a06f4c769 +b71963777535b4d407286d08f6f55da8f50418486392a0018ee10f9ae007a377b8b8336f33386b0eb01c45695c3ed2da +88dc87826941340913b564a4f9b74985a311371c8e7b47881235d81c081f1682bef313c2f86561a038757fb7d6a1a8dc +869e13e3fcf91396750150f9dc9307460494c1d365f57893fd06fb8acf87ac7dddc24e4320d9cad0414119013ea739b8 +92194e292303d32b91ae9cecb8d6367c8799c2d928b2e2846dab1b901371a4e522fc4089aad8f4ee676f0614ff8b19d7 +aa589a3e512cb4f8589bc61e826a06d9f9cb9fdfd57cf5c8a5a63841435b0548e30a424ca3d9ef52bf82cc83c6cb1134 +81802e0194bc351b9a5e7a0a47911d3a0a331b280cf1936c6cf86b839d3a4ab64e800a3fe80ea6c72c3751356005a38b +88e5e9e3c802314ddd21cb86f2014948b7618502a70321c1caf72401654e361aac6990a674239afa1f46698545614c93 +abac1e0f85d5c3ff6d54ed94930c81716d0ac92be49e3d393bed858833f4796c2b80bf7c943e7110de7b2d148463bfbf +b7eb416004febd574aef281745464f93ef835fd65b77d460b6ad5d5a85a24b536b4dec800cfe80ae98489e54447e8bb6 +b3fd8ed1c30e7c15b0bc0baf0d9d1ecad266bafb281cd4e37c55edc76c202fb1e4ea315a91a2848f40f481793ae35058 +86ef674ddf4b7d303c68bbfb53db00b925ccbf11d7d775ca09e458f4ecd868ca828103e8e7cd9d99672a193e81b83923 +95ef414e9f7e93f0aaaeb63cd84eb37fc059eb8b6eced2f01b24835b043b1afb3458069c45218da790c44de7246860c9 +93ec8f84c20b7752bfc84bb88c11d5f76456136377272b9ac95d46c34fce6dcfc54c0e4f45186dd8df6e2f924f7726ab +95df5f3f677c03a238a76582d7cb22ed998b9f89aecf701475467616335c18e435283764fb733fb7099810fec35932ae +8cda640695c6bc1497d19b9edc5ff4ea94c1c135d86f573d744358758f6066c1458901f9367190dcd24432ae41684cf0 +b19aedf5569435ff62019d71baa5e0a970c6d95fe4758081604f16b8e6120e6b557209cdea0ccd2efec6ff9e902d6ce6 +b3041f21f07d52e6bd723068df610aa894dfdde88094897593e50c5694c23025e412ef87a9d16cadd1adbb1c6e89ced4 +a7f8d6ab0a7beb4f8d1cfef6960ebdaa364239eca949b535607dee5caeff8e5dfc2a9cfb880cc4466780c696cff2c3a6 +99a565b4796e2b990bfcb234772d93c5ffdbe10453b5aa94662272009a606ba6ea30cc0c3c26aa22982c1e90738418a5 +90c54b55ff19157c1e679d8d4f7f0687a70a27d88f123179a973c62565adfcc9347cfe31f54539038cf2f34556c86870 +8612f34bcd018d742202d77d7ce26cf9bc4e0d78e50ddf75250b9944583b2c6648f992b635ea13fdaae119764e7c28d5 +a04fb38e5529bf9c76ec2b5e3a1ef3c6f9effb6246c7f67301cfed707356ba1bf774f2867c77a5805933f0c8ad0ec644 +b4800e7b503da0164885d253135c3b989690794d145182572181995e6fa1989f3d0324993e871bbd5f48fadd869d8a18 +9981cd4f28ae7b7dadf454fb3aec29746dc2e0ca3bd371b2a57cd2135a7d93559e02132528ccd2d305b639d7ac51613d +a3ceec012dd1fbad3ef9f9f1d6fe7618e13d4d59e3f50540d2a57010d651092979c75442ec8b38a1ab678505e30b710d +8b97b8654d067fb4319a6e4ee439fb8de0f22fd9db5569ba0935a02235cb4edd40a4740836c303ec2394c59a0b96308b +b3d1bf4410fec669a269622c3ce63282c9ac864620d7b46c9dfcec52d8e79b90c4c90a69c32763136a7f2d148493524e +93174eba1e03f879e44921084aa0ee3562e48c2be49085de96ed7621c768ff52324d14c8cc81f17d7ed50c38ffb2c964 +aa2194cd0fb7aec3dac9a1bd8ea08be785926ed6812538be6d3c54218ea4b563646af1f5c5f95cb914f37edfae55137d +93f2c0dd59364f6061d3da189e04d6c64389a3563b062e8f969a982cd68cc55b4f38b21546c8a67c8df466ff4f61f9c5 +aa7dd497cc949c10209c7010ba4ce8a1efd3cd806a849971e3e01716ea06a62e9d5e122ad1d2b8e5a535fae0a01a7761 +ad402424b2a32bca775a66aa087580d7a81f0867f293f1c35580b9e87ccc5a2bab00c29a50fd0d7bd711085ae2248965 +96237843d8e29ac77fc6ebf4acc12946ad11697de8e5f152fe5776f2475b790226a7d156ac48968dd68b89512dc55943 +a45c25cdbb9fc327cc49a1666988af9ab4c5f79cea751437d576793a01c3eeea4c962c05c0947852fe0e4c63e1c84771 +93dcf834a614a6f5484cc4ba059e733ab5dcc54253229df65ff5ad57b447353ebbc930736a4c96322e264e65736948dc +b9a94f82a82c0c5a26f2c1d5381afec3645e8ee04c947dc3b7ad59a73018db1e9965ab3642f2bbf60f32c430b074fb22 +94eab29b3524ccbe0c4b928e5fa5dd8f684074b332fcf301c634d11083653ffee4f7e92ddbcb87ed038024954ad1747b +b8dca5f679931d6abef0674bad0639aefad64c2b80572d646aaab17adf5ca1ab2ebeecd5a526cadc230bec92ed933fc2 +944d394958e539251b475c4304f103a09f62448b7d8a8eaef2f58e7de4f6e2e657d58d5b38e8513474115f323f6ec601 +8a5ae1f13d433962d05df79d049b28e63fe72688fc3e6660aa28e0876a860c3dbc5fc889d79f5c4dec4b3a34cdf89277 +afa5278724998eced338bb5932ecf1043d2be5dd93f4d231d05d2ea05b4455f2ffdc0eadcb335dcace96dd8b2b4926fb +b91153a2f4647ae82fc4ee7396d2ca23270ec7f8884ce9eead7e9376270678edd42dd3d4d6c003dfc2dde9fd88cc6e7c +adc932f1c679bf7889cb1ff4a2d2897d7973483fa283979a0ea3640c80ed106ea0934c1961dd42d74b22504be49851f2 +a82e90761fae684d1415cee0649bb031bcb325ae0b28f128ab8e3650bccedd302a70de1a341ca8decfdda76f3349cad0 +8ae353188b4b98835f4ef0333cccb9e29e1ac3ec11d554bc96f5880c101cb3c84b8eefe72f2287b0812735339fe66cfa +b8b41135bb1a1ffb64afbd83e2189e755f2c350e1273cf47c38ae9b8c4800d831436a69458b8ef9fa8b95a148d8ec9fd +96f75a04d8752fa93dc1eaf85ad333cff4eeec902a345576139e16de3a88eeb71b6726224349bb9844065cc454d959e9 +ab82b05e3923ad4c26f5727c60dc0d23063c03f5a4fd8077da66aa87042cad1bd99586d4ab35aa5e4ce6f4da6fecf3c1 +a50c83db91c26ef7bf1720d8815b41bd056b49fd99710943679a162ccf46097a7a24585750ece886e38eb4fdb866fa37 +a719f667914a84f62350dcc6f4f30b9ab428eac6837b70318c3ac491c1e69d48af5e1656c021818f377d911fe947c113 +a148807aafddfa0a5624c7cb9e42468219e4bdb9994ec36bc19b6e6d7c4a54d3a0763d13ca80624af48bbd96d73afca5 +aa012f205daf22a03e9fb13a63783dda7666f788a237232598d02a4d4becec7a699ab493f78d722ce68519262924c708 +97fc15fab5952c5a2d698fd6f7ad48aff1c8aa589f7d3b14285fea5e858c471cf72f09a892e814104fa2b27eb9771e73 +8da8840236812667c4c51c8fc8ab96d20dae8e2025290b9cde0147570a03384370b0fcbe20339c6aff09cca5d63e726f +b477d85359a8e423fed73409f61417a806cb89c9a401967622aba32bf85b569e82bca1b3394c79e180114a0d60b97316 +b3d6ee2ed1e4c5cf8ba2c3a4f329832e41c7fdcbcda8a3fcbe8f60967fdb1717665610b7c1ac65582534d269d762aa09 +a0b3b30b1b830b8331ee19f96b4a4321a6b93a3395b95d3a895682c65ec6ea64774b878b93514eaf353f2e4be28617b8 +a2b88e9617f4d30ef4e686d1932ad43cd555fadcb5102e51bea19e6fca649284ccf4debb37b5cb2090ef386fa5bf5327 +8a4446f7e8463ea977a68d6217a9046ad4356d6fc1c18d46c5d2ab681ea977b8faff136d65abea6bbf8936369cb33117 +91e7464bc56e03f436228104939ddd50caace5a38f68817bb2991e193b57adf6835152bbf3dbcdebf0382ac9823f60c9 +961a441e6cdf8106c4f45e5b47190d35644faec701c9cfc41ced40cfdd1fa83752fd56c1ac49131a47f1970a8f825904 +94b7b165cc71c2ae82976b8f03c035fb70e90028992b853aa902c0467b384c7bcf01d56166bec5def4453e4d0c907e52 +a5d32cffabbf547f900026b34ef46f08075b7a244565f615370d2f04edf50b094c95088a4a139ce07caf55bcd99afa07 +b4e06e73660745f75ab2f34d9f6d2675b58f80f911ab6dd4c5a6ce1095f9a2b50d86f6ff9a05394190bdf96af0827920 +ad3fd8f83c0103b29d41319209dffca201d2b98094362da08da3fd6ff0ba96796b49d6bed525c9adb96c2954858e7f48 +b0c27430695f0fd20ae31e1ec621da090094f2203e17411db9384695ffcf5c7c6badf461ba49ba70164aacebd6f278ee +b9bc6e972fc3b532fd2b1eeafc4bceb77604885f32132af6a9a842fa2440df452f49ec0cd9d86da1180e8deb0723b260 +9729e22d6104b0174c136a854920f542b384d375040adcebe36acc253bdb55845eb43e34dc5a7cc27d22c417973c24d0 +a8b420b36d48786c9231d454468a6e855dd7f71dcfd095efc9855ee70dbece0f06ad277f7829c5813fc30524c3e40308 +8757dff5499668c93fc5d9cea0a8db61817b8ed407200d623030b5849a913d12f8371b667cfde8d8082026eda7407e8c +b859ad747ca5af661fbd03a1a282df6e84c224ecea645bc2d4ba5e35fa06cbf047387319fca0cbc76b712398c0798968 +8e3173c27875f1460297af0fa736c945dc842ec3e476a973d3d5f790bf183ad3ffe96ac13868c5101d8e299890791864 +a9d725e2b92c878be42b5eecc2c3081c63c7231ccc7e2dee17ca6a4caaeae22788fab1f1465fcbd7fc236613fc2bae4c +86f6c4f04a354cb2470ef91914816fd740f8d5795ce7ff981f55a2634695fde5951bbae7a4bbc4c63747040f8644170a +851773cb26f320f0c3f252d95ea7e058ffcc795dd0dc35e459aa1b6b448238909230d809e82022e64b7fca5d40b8324c +8962641e0306220d9892fe2d452caa286301a3c465185757be7bce2d9b2c9beb3040280099606cc86773e43941fd3439 +8beb6e08c440b0de5fb85251d39d9e72db4e556a2dfe3dae59efd8b359d08492064cebd8d8993254b43bde8bd67d969a +a7e047894466ffe3dec4ab8d5462f2b1d8ac0df006b1d2dd26caf499ea857d93a811cf42233f9e948c9cb903beec004c +92eedd95557a91691a5e2835170390ce2401e223da43b78615a804c49566f9d31cbb7f10c8a8390c4bdcf691544fdba9 +a5e5b5d8fa65824e958bbae98d146b4b332f97ed50e0bc2c58851dc2c174ab71bcbb1ae015cd2955c26b368487dd862f +853a494eafb308175629d581ed04bed71bbc3af9ca4c0dc483d03d27c993a2bbd88cea47c2085a6928d166fe6938fb77 +83f06b88d29afbfbe8f61811690322ac4fdd6abb9a23612162e7a2dd6bcbb5f14cee298ebebc1a382484f7346dc51e60 +8c9cf05735ea5a0e563490bdc7ed29a4426643711c651e35c8551ca6f855c8458ae8f0933a022d0bb9a952edfed411f6 +b906b48d807748a26cc2a8848455a76ce502261afe31f61777b71917bdf7de2fece419db636439478c7582058f626c29 +97efe1fa7c9b25d8bea79d74b6cdcf88f63f1e865f54b58512a2e60428630b0b40b8b6af1b5f71df47520507548c3cad +8ef5ca6e753818906bb3fc71405928d8e4108854ef0ef01c1009071b353bc2852e771fcb619d5fea45590e8f61003d7f +8e4d901661e2913740d70ba4d0745df5e8c9c0a260149d9362beadc7e669630ba909ff0e8a6cc85c54d6b7435d0d351e +b7c6ba3bebbd9592967954e3a480ee8df1d9f5965f04e7d78a5415b645128deae7ddaf6ed507c8877bfca91ce078e529 +840bedb0ad4e25acf6cd25dee4f98fea495b2312dc5cb7a8388c5ab00b2acb9cd25da08e9fbead145a3107972b1ccd5d +a8d4578dbafdb27f3911af59962d89e75dea74db55346720357790da677312c203107d9c7911535aa563446fde7d4c47 +86d3b77f231bfa09251b7fd2ce09c27ac520ec35d783e912476f9a4863f83d269eb175790d6e735da9260293d707f8ee +b34909f1cc033232652da0c34051a769dc76adb1aee00674a59dc1b860f6e610974c3b4bb69a69ccc73e01f042431242 +90799854d0cf34e1d91ff8e101bc7c5007423d34d2f3bd9adea2ecac57e83f3a65a506bb93d4caea49b29f6d18149957 +8ef94cde29b037e19a1ce7bf4418ad3c95cd9457412796ea385750c19a6690f13a3bb5bb6a9ee81e7a40face1e0a8bca +97053d21ae8d75972fb37f6fe516c38c32ab162fb56b9f510f954858f4e3ef6ac8c3a9557ed3f41b7b6aef05fe97f931 +90a9f9f0f40991f3bddc58b92d40382147db22cce50d092d4a05aad251b46b94e71ec9f7107a180243288059fcc5ce29 +a14265b1344ac2921b0f890d13bcfc432e4f648ce403e261fce4d3bb32ffee9e2794c02830346054f998e82784c77040 +91928402ae121e56a3e64cd6f390127e6e92fbfb1967ec6efa4f52f3e8058f1f41a0f4fe96b5bcc11641c1139e790b2b +921c8c92b6d40da6c5a7b592acc74fc0f577d93767b9aa4a1cd302a72dbf503a1ea5b2c29fa0d0359bff3b8f252246d1 +93ae0ebe0e8e133fd80cf67a499047e30ec4c4660ccec9d49098717ef57721a030f423e00c5e74af4ff4acf014a10497 +82c865e21905aebfe0496af1c6ac7e342b5f446a9edb4f7da0f2fb0340abfd8e6fc545da874459d9aabe6bce0dd9bfcb +aee3961d8d2687c0f134b9c28b920bdc4021d925fbe14323c84224a9fe161248789249fb85436a5891d0bbff42c2a3e9 +91aee420b98b6949482b8ff4be996b97245b4e8f583a6e085226539074f42aa89818395efd1a6699735a569bfe19d623 +a48eec22c192e495b01722d0016a54acc45ff837e2a95c4294ce81d5a4e43e0053a6f0ead8a4fb3ddd35faf6607275b0 +a26e15937c11faa30ffa64817f035e294cab0e839f73d29de8a244ad039be4e221eb47ea08d9a4658b0152fc3caf6110 +b84450f948aa7c8682fccb9cae84d8e3558adf2d0ca5fb81eb200415291158720f8f3470542ab5b88c6873ad08e7fa9a +a8e8ec27d0608d020169a85d6ecdb40eb402f006a3b97afe32cc01987721b3a68a92ec693aeb4d357e189e05fadf699e +ac87cd535ef5699312cc26f86adb71baa0be42e858bd5a2d94ac05737dac63430691e29b9a30d2559ad581a172519b2c +a4481e67b524f8cddf2046625efd3d75efee6aab87ddd2c1b22835647e918157e5e924ac760db2195c86d326f3db1615 +891f29ded231486ee826840c8895cb325f7e84a5a6d2eac246cb3573612cde274720233b1978318a57ed337a046330a6 +906b6e750e6178289012769807d2598925d7e51c260c14497d8af978b1695990e3352e6e809a752f376597a68083870c +b7a056898ee1e46f7f29702fb39232f678ec173eccd170303b3b0a30c8d8cf1a5321384e3513e3b03bb742c238deaa54 +8f2f035fd96c3a336354c89ec9b8222803bf42e95fb2412c28d4e75eec99c1d4d402501ccae17357b757db8bdb0bfeab +81228625ffcedf977fba9cfa13f6edead3985e2651d5974789c394a69401cd7face9e20ae6694be4c0d4bab5e99c61a8 +885a83eae25e61439ad809567a2ab148583402e01cfdd77b0e37ab4038935425c64b4e0886949bf06438c35e80aa13f4 +8926387f48752f6933899c48e038cf14e7941ec6a58bcc0a436614b396296a17aa53e6873803dd3041dae470bd493fcb +95d0d3fa061f4d856eca78a569aa132db14cede7646f97e2aceb6da0c8ea53195d3b7a566fe5ec8c41b95ecdd89a1c6b +a3c817f4062ed6aa94064ea695d76c1825f3bf77b310fe1db28b8bedc9aaacbf1019dbd128adfd53042fb943d863a2b7 +af1208417aa584052da309169854149ede38a3ad63c76cad6e43afb6f1a7b854edf8310a0b00088c039259cedf0f859b +8b713fc3196bad35dbf364089049ada5477e540d78d76a5f0a9df98f7ba4a0e65dd0644509c149f9b07887298bf74b04 +89c09c43c5b733c4a417cd9ebc0795cc3348b72778d31828a9171427779a82ef023c1a4fcfcdc919ae25056f9c826fde +a0759c850ed320c8c874435e90ace6edfb8e7b3f2a09d942b8ad8339c508044ee2ee26c70f1b626ec49a77971433b6a8 +b85cbc58d4fd52286e714ac4eaaa0b2743a1de06fa03ddf8f6668ec6f1d204acccce93b10620272afb8c0b49bc4b0a43 +814e0a87384e159892a8d23036985fa3f489c53bce192e107bd2d64f57b1bf5ea0acc1ef46c7a42bbc5cd0924d92b4a0 +aa6821da96ad89d7881b878e141076522f104ea9a5bbdd1fce9f641898f7d6232c518a87a0f666871d7e3165c26081e4 +a9041d714bfc067b5427252186fa3557bad598fc0067dc8521aa9bc1ae298f6e96113db5ac9f6bade9a85d5a950c9755 +b8669340f3064692625e1bf682d34fbe69a61689e3aa6d6a3e822c781d406b0300dba9c3f7b8152a8c2513f1310d4291 +a78c53316ce768a1dc5968030bf4fc885f4029b1ddb6a5d84a61c85af686c73727f62823891edfcb6ccf4545de366cff +ad1d3aa29ea28292ddd438c865e2b5d93f32cdf009e6d5f5dc726de996583925727e6348bf1c28c22dec0bd86aaf867f +ae1447a2062e9e28af5f38aecc60fe150cd10c2edeaf2110034aa144f6235ed7fbce432a58805d4fe1f6b12652d6e1cd +a32146634332d3303934550705353c6d4fae5fa5985105bba35041e74cd71e2aad67b45da171221f6ed80f36bf6dffa3 +a232e8286184196ea77427b53d8b52c44d758ecc42d22556529db3136379b4989dec61cff610cc6cf6700a450a847a94 +8a72c7255125a736da52dff5f77e44c3de29f88fc05f5ff9227c69df296930caaa11446595e6bea3bd946baac5ef957c +9688a981a9457678067f629f8efa6b522e7318b529f88d37ef56c5bf8f1c34fb9bb3a918ab73caab82bf5abb0c03518b +88286f3eabd71115fc3b17a6bf6981340a81cf7e5f96b0a1a016d4ec8c18fb486d46c70919123d0c189a6f5d6ff29a1e +b535e701b40d793c02ac0d625ca91620d3f4a512aa9741f71389e58381008b2f93d597586d06213c4e103d67d0ddf6c5 +80d0c9dd941e8d8d3700cc51a434a5aaa3308cf8ebfd14128ccfd258f826b27cc3cf5c3ad7851340393abb1eeab3a157 +87049225fa2380d93f18d3d90cb0697a56b373b66d7f24ab209966aed8b55a2790194d5885399db29dd5b1f189eda64f +a52df158ce8670e0290551e8878d63dd33b4759d6f50e448e63fc7fe6ea99dddb6f180be5fc0fc3918ce54c05f80b356 +8b2a728b39c465fb0f60b0c486e5dc8d5845ccec03d3dd93b393cedeeb3fe1b44518359f1ed55fc770a8f74bfeb9923d +91fc05419dba718fa4a910dcf256ebea356bbea00522d8d5ec3e7ba4271a26035aac15e8d9f707969df1d655d92dac55 +97c8779ae80c24c1f82d5a714762d6ee81069224e39515e41d8a71c9310dc5d1c55cc92bc5c6a4bd391ae4c321d1d4d2 +b5e5aedba378c4484e3a7a4ed41b75b0844f674261c2501497de6f91f7274b5a4c1be0e055f2e0c0cab843d891169fbf +8a26212f27211b295beea500abc8e9d430a8500d3a350cc62f895d39e8b4668aa638c17633804ba353010000165637ae +864a95118e5d394e00e99efebd505df0125525c9ebe165764c453b80ad3edc730feebde3d93850745dfd88a27bb8f20b +a092e0b78290e826cc1ae56afffdd08f7c10954f549a3ea6666f3db1b6cdaeb7df53db28dd2a92446342930fe60a27ce +a1720224c0626a081b6c637b2a6d37da85d9a82241e5efef3bc15699b02a69f6304e43d8ff3144d60c16e00225d6b39e +a7b3d098cebea9cf32e19c5195608182b6afe9d4af6b9df532c047eb7a941a971279b2ae6a4b80f2f9d9313a6d788ce3 +a3d2451e6788944802c5077a778d7b7299dbb9d1612676bb6baae78f39976e0fd879493cc4a4d737b8174b472a456850 +930121b73da844571b1411d56760e80923a4ee09917b3e9cff4d3dcb0bc27026ff2c4e2c44e7aca7d3f8383f129c7f9b +b4b0119d163ee00a2b74bdf188a5cdcf054daaa48c483b94bbb4d09ff615afb4a91347db6363bc7535e2af9054ec2214 +a5846decee706780201095a8cdd48fbf3d3a2eac8d089a818e5e22c29457494bbfb4399323b067f3d2be2197c33dbd98 +96ba600df10ee7af5a9df29c0ca31dbed275d647faf9c66c7342de927ceb25b5bdd852dd7aae0228b27897f90fdd5d62 +b6ac51ddc98edd9fb9f54ef84bf372a041d58dfdf0dfdbdc4b08ddc1a7ba93ddbb1413dda3c1545a3fd7386c6b85975c +b35f3efd91a0723e0d486188ea9675a3462106470455118392d7610470b623caca2fa33829721c05fbeb0fabcf570bfc +87f49e85df5f8055714a8ce7adf37f6a278e64e76ed74c60abe3edfc3611ef5b0426d4c6da45e5f3b74d30be1dc6f539 +8ff8bb06902a71b1e9177a77367318b2e3e0a88f5d74d6907ca9943f4f9f1ceb5f297132c2a025259d17a67e880d1bad +85eb6de6c70fe5c53ab0ab27aa0fec439f136c979c557d317337cafa6e6c5cb3169679c9169567dec5f6c72b3c057d83 +ac18715ed1080771d760cb7066c6328faf65d9b30517903f8a5cad8d66d5c6381156b521107d7cd75ebb8c30e250706c +b95b9eae4703727e4ac9ddf2ae675906487bb78905a5f9cba74a4cbfd118d96b7afb6ef3ed5edf14fd963b830d71338c +a3b47b52fda16b62b11c8aa4daa56b0b669c4d5c56a3059b7d063284d8a91f6fff9ccccab23d6ceb9650483b2d353039 +96a95b3f327df94c85e92f2e406f1649ac621533c256b062738f3c3ee137059a735a3e6072247acf57b1b0d8c219bd7f +b19b33cc04570be94eae8e943d5bb17bb0c96e9de4ca84f9f41b37320a1a03d397d53747dc13275fef1b356de557214f +a1faa3dcb931dd91507f3f12a17c43f6627fa2bc5c71fbdd27548e091eaaaba262477949cd51290e81196bffb954a492 +b060a16079dca1d28a1fb33cbc26f368630ee042d980ce305230005d5b9ab533a7a695281ab76e9214458303932d8bbc +b303783196a858fe45d67e0520c30576da605fd69964449c20009fbd5099cf1de52a32d326d7c3b864de07440195ef40 +aa550a4c20d1003d137ffd8fbdc1196d09ad53cfa0e202302093a80fa3bbc4c9aff83f34f2151785cc1ce5f30255693b +a7f8585f45566a351058e10c6f1ff4a7ba24811f1482a47202f581525615ca770da93f2f58878788b45b92cb446ef4ec +8206f63a9a5b59bd68e64a843e68fcdf706f4c13bbfcdfa9928298e5b9251006ae0bbd80c715aa3c9957d2c0148b5059 +ac9490abe1241319658f1c2c645cfa01296f5d4106020c7894b7ba4a65cdd52f6c5401bd3b3cf1c9863e088cd8c9a16f +85dd6d9c80a1b58c24c4d2cb7590d33d2454f381f58e820979948e5831972360cde67bbd56e1860077ef5192fcacb904 +8b0285944c676fe2519cb68da0973275fa29c0718d838d363ce46651b068d29f867cf9fe579ff8da0bb8b37d202bb23c +95147275da658d43a758b203b9ca1f1c1478853e9bf77b5218593142e2bd9c0bf46d2206ab64cef99295de6e9a268edc +b8efa187fdd3e1f46c15cd596e9567690c10e253b5beaa5be8074b6ea4e6d3d06e0f2b05323453239e419ae1e7128521 +8340464f52c92e31806fd3e8e65f56e27194d1f6daa4a0f0b3831e8102aba16f88bb5a621633ddb7dd0342e1d2d12343 +8615d87dcab85a78dc052f05a01e751176b756b5dc9985014347454ce5752f459dd6464e1c5aff36cb6c51b783fa2692 +80c6e35c0d3defbe4d3968792724a23f0b8830dd2fac58663583a49339ea20f1812cc4140e3ee867c7e716177319bbbe +a7aa63dbfc201dde8f29bb6e23d7aa5020dd35bd18a0cc93c8a10c35d695913fe25b9e8cf9b5fd1899e9657b22bc8863 +97c2a4ba80c4caba2e729a603d2faa0120915e3fe64cbb065f7ff33de5f877f1ec9461cf455e88ec9e9ded9393939dba +a54bd1419f0e2d2d87757870f37c476c7e3a13502f1ada82fd7394fd29f8a00c4986473d753034d0954a2550badbac0b +8d3e2bf900d0d2b9b46e6e2f37620f0cc90526dbbcfaad4e4a37ed53f39fdd23bd3a6f21aa7e800eaec937d9710dd6e3 +a88d2b1c7802b2dc216c2b6532406c091bfb12f29121b9a82c1154470e250188413ddd3e79f7e009ea987a4c45b332e5 +8c552c2101dfdc3f99c2da436115452e4d364eefe029b12946f05673c5ce1cfb48d39a579625849236dc6c8e7277dd30 +8415c252d52a26a6400c3189c928a98559bf24162ecf3eef1d10e439269c31d854b0b4f6ec7a2430e3f11b5d77de78d6 +8b38905bad93a8d42339dbdb5e510003c51fcaf05e04f88fd7083753353bc1c4c00a5dd4a67431cd4456d0669c7040e2 +b1d0ed8862250d0f0d9ef9dcf0cd16d84313d1a795dc0c08e0b150dadf9ce73d32d735e04632b289cafa69a6ee75dc89 +9434e18a5fb631b10edb02057f2d1fe16000ee55ada3c26a079c9fc3943e29d6de99e52829fe7b333e962270c712e51e +b1b9f3914007e6fca8ad3e7e848a1108988cb2318da36df24767d804e95d1272943fda948451135cc1b5052a3953b081 +8c02947a76d7b6c0a700a83dfb971dc105bfe996e18c521445f036310914b349ab28e57571e36ae08d13a46fb01c2f43 +893472fbc225f973a0ac6a0a0130b9cfb7ab6869dff80df71a62b1f6beb4afd069bbf35b4f327165bc31dff39e4fcaa4 +a7c176c0903175f3540d62f9afee994d5d9bf37081e094644b22f017e94c515afefde7bb07f638342abef7de657f8848 +860186c2b1d3b1e657729bc804275fb5f5ee89eaa60848fcabd3871289665ea9f0efc8a95792d884972bcfa2de96223b +865b38aea6386d0ac8f501a7d934e23d01dc50105324e354d4c4fa3cb1d4c29c26f4566df7b1a728e10cfaa9d24552e6 +b4eea5548de6969dada658df604b5d9c49002e2258352838003e0fdf7b299d81fb025807a7f37cf5b547cebd7f2c1f93 +8982de11ba68d63a649a3b296d4d56c71e3c3eec016db250d733ab7c3b9a620c09c5a5d0b64fd30d3bc03037ca4b17c9 +84d8b8a10d67eda4716673167c360fc9b95717cf36ef1d5bc6f2ef5b9d2624f0e76c2a704d016adf03e775ea8e28d83a +834d03ebd51aff4d777714783e750b84c16cb6627f8311bd8ff17c3b97fc4a5bba57d6c8f6d74f195d3030bcb5f07612 +aaf49e0def0c4d5f2c1e9c17b51e931d2f754b19e80070954980b6c160178349f6d3c8d4808801d362e77f41a0008918 +8ef4115edec841854e89f2bbd11498dac7396bca35dda554290d3db1c459ffc17be671f4a46d29fa78cbd6064cc2da20 +9641dc8a64f4acd38e343a3062787c48c312f1382f7e310ccea3e95e066ab6dc980f6ed90a633236a435e68bf6b3c625 +8a84cfc2cbeb18a11dd6c2a0aebb3f6fd58a33bb4b26101e826add03748595022e816afac79a4e7c20b3805252839dca +9770782d729017659844421e1639ffcda66a2044df9e19769b90292df87dcb146b20c6b9141bb2302029d84a5310665d +98c7ec9696454868ac52799d1c098c15ec4e08b34884dda186ebfe87d32840b81fd3282295df141c91137faf4cc02da8 +a3f6eb921247617292162dfc8eec5b830ddc294a0fb92f5b4828a541091ffdaff34c392c1d7168259d6204405d90ec72 +b185f77a468f07a54222d968a95635234e74fc942485604909308a9028ed2753b15902b9134749f381f7cd6b89cc8c3d +867608a682d53bd691dbc92eeb460d1c300b362ca49c11a280f6768ccec217f1145f9d59fe50d994f715ce89d38a74e1 +afaad630ad8827cd71aade80edf3d7aeb65a344878db12fa848759e6233f6fceca563aa437e506ea9e0f1e47b126d45b +a12afbc84e3441594aecf85d089423dd3bb8bb33a1a384ddf7cc14caa72284caaa56aa179c15e3140fd56bb532491a67 +98757b0b5e5837ddc156a4a01ce78f33bb1fce51e0c1254ee9b6d3942268d0feb50b93edbf6aa88f9ea7b3c0309830d8 +89573f4a4ae752e9f964e42bec77d28a41840c28e4bcdf86a98a131d0b85367b885077823a6f916972de6ac110821bd2 +a17f2745052de5de9c059307308fc49f56cb5230e7a41cb7e14a61c9efa742ee14c41023ce90c7f2261adc71e31045f8 +914b07c53a41c0d480083f41a61c10429ea42dafea9a0db93862d2269ff69c41db8b110b4768687b88089b5e095523cf +b380cc3e0d26370976fe891d24ea4eeb1b6be8cfce01f47fd68838a27190e644fd57b049d3aa0a9589370de20e276944 +906385fdfad60feec79eb1c303e750c659ceb22d9c16a95faaae093daadd53e7aa039a45d57e20951d6e1ca0dc899ef2 +b5211ceee31b194dba60b616bfd91536e71b9213a3aaaf5aaf9b2f4cbdeb05191861d78b97eec58e3c81abe4f0488c04 +97878e9e38c2f69d697800e7a2f132fc4babaacf471c79c26a757f771606e55fe696ece68a3163a0ffeb2f72274cf214 +959431c1f54c46500c05aaa9a2bc4230531dad97ae768fa92bb85436c0ecc6374cf20fb0ef82d122db116820a943b401 +b69e5a1c6798f30d33e42cb8d124f025d2c77c993c4c7107a539aacddf44d8d4d2239e802ece32e60ee4dbfdce201bdb +a8b09e5e9f802ad273b2efa02bcbc3d4a65ac68510510b9400a08d75b47b31c6f61ffdb3704abf535a3d6d9362fc6244 +a41ace7f1efa930564544af9aa7d42a9f50f8ba834badcaf64b0801aaed0f1616b295284e74ca00c29a1e10c3de68996 +a8f2aa0bbbc19420a7c7cec3e8d4229129b4eb08fff814d959300cd7a017ddb6548c9a6efebad567d5a6fde679a6ac6a +9683da74490a2161252d671d0bc16eb07110f7af171a1080dc4d9e4684854336a44c022efe3074eb29958ae8a1a14ace +8ef44d78d10795050c161b36afa9ab2f2f004ccf50fdeef42fe9cdc72ebb15a09389ca72a00001cd6d9b1d7b3bb766c3 +adca54f3b14fb18298098970b0267301b7312afb75894deea1b2afa3e85b7a3b4efac9971ab54c5cbecba2da9f18507e +ac5d4528f06fdccfc1370d5c3d03ed982fed0861a93a3f6453aa64e99360b124926d1892faaf72d89459e663721dfa99 +98aa1c801bd615b8cba728fa993021e181e0ad717ba01c0290e7355694155407083eb53cb70819c4775da39d33224db7 +8b3aea4c7c2bfe1020de3261ec085d79c7bf8a7903b825d2c70ebbb84af197bcc54e3653c5373a2045c3021526b63b66 +a29f3de4cb3d99afff1daf7d431b38a33a9804fedc41626618928ed059df6f6fe9f298a046b594ffee951ed4d4e1400f +803fd346be540c5242667c18ee41b26bc812456ab13ff117196ed69b90ee608c8cb6554396b64066a546ec87a71ed6a9 +a9c18d81ffd029c0339c72c499bb51685392253b996b6eabd8b76f05c6191ed8444a1397d63b9923743661a319517f7e +a048d5c390d08f07161faac71c5994baf152c883b205f3bb10d3501709d6516ae54d491b486303a11b751857a31f0052 +9156fb4803e40e28d8d57d928481a8de4373687288da44fe88c5676a8ae013ed1fcc09d56a31140bf74e7f767253810e +98e289c725b18e0085afdfaf2acbc674dae7b0a2ecc2537a7d0b87e20eb785404ab05973a787f0495d2adb3e5565c09b +8a7237b249325bd67cdc1f9fb278710069033c304afbf270b7ea24dbc10c8eabe559a484d3edc733c77b4384932deb41 +9056f2e5b02e5c2e04a69fa1323bbf1859d143761268d18e74632e43800a2a9c76fd681e924a19bc141de0e128d3e462 +b9f2bf9e4e7263014296a82b9ecbb05d3f1efa4b2e675e3b38d3eace59da06a89c859256e1b77847886d6aa15f98f649 +83b22949cca19030289bbf7cd2a0d8b84e1d468e78bc85271a6753241b89122627632723bc293cf904a5eb2b5dc6c3ae +a919aaf35dd0116168d2ee845122026416bec9633df113fbd913d8db5996221e234f98470d029a8ff182825b59fda20a +91726901f49d32b41afa15219073842278f60dcee223640903d871e318a1c2b541136b7b38a7b2ab7d31e4242fc29674 +942b77666545bc9a858d36cfe857ab1a787c9528f4a0b87918a06bf510793264dcafd12ae6bd3ee300179dab7f40aed0 +80adc1f2f9c47a96d416e44fcba41628abc0fae1f88f6a26aea4648419ab726f7fcc2187c7d5145e3d8f5a75c03937f4 +8041e0f66ba9dcee01e336dd4d16ae5e4e1618512fc147cc8230003aa2940848162dc2187d4130bf550dc1f3559849d4 +999e8adc51bab54386af1c5e8822986ad1b7ecaf1f8a4c2baa5bb2fe9d10710e49545c5a8bd89ed0e61a3d73a908e5ef +89272ffd39b6e9f99fafdd58bd9dc00f66f26a1d36b38a1ac6215e3546d966739eecda7fc236335479207cef95cce484 +b8e0b7532af13f15dc04a0eb4ea8abd67e58f1b1c6ad2e70c0ffa04a5c18ec2018b5d7f4be2f9f86db5e0b3986f639d9 +b96bd11b0f6ead4abd5fe1e4c6e995da7583b901afd01cc05e87d04663fb997997d6d39dd9fb067c62cb1b1cbb67516f +94ab08914088b973e8dbd5685decb95f3bf9e7e4700d50a05dbf5aaac9aea4be2c10c83096c02252e9238ceea1351d05 +a188de419b062af21275d976494c131ba18d2b2ead8bdbfa38a777832448e64d4d9725c6a1d530ffb6513f18d5b68d9d +8f73c8c118fa25c76a4ec5611351953c491452743056a819c8c82ba4737a37d88da0b55f837e7239a5f46d2c05a1bbba +894a44769e0be1c26648b0d89c4c9f46dbdeb3a71b90c493093bee372bb9f2d3f319850fd886d51f4f58db0de5641742 +87d239923b0db024a8d9b0281111d47b0761d81c50652268b074efa3ea70d793e30f874a91ce33a4acecd0cf38c01951 +b1b48b75a97f9fc2dc9530dc69f6268829dd0ddd574516e7eb1b9f5c3a90058889a7bcf3d378738e6d4b02f5fbfa44db +83e3ee9526ffcb60c6e75b75550fc017912ec0daf96d0a0d5f58c1b229cce90c684ac7c3e17fb998def8e7e2e155d750 +b9b7bba579e474b0abdc7775ff5f84c9f117c6ca17788cf5a5f01b2c35a14aa39036031c8d799fec2cfb371d9f7471fd +90d7faf4891fbc368a32f575dfb69f13e37161ab4f63a7139be103285a49490c2851a907f8d36e09e7d1a190dddbc6cd +968c8b9affe18fc34a4e21f0d8c5518341c566099e6b45b8721c9912bab3693c9cc343406fe90279692a1eef2a3f7311 +8735baaf4704207550f77df73fb701d9a63329993a8cb355ccc0d80daf950145f37e9b4b22be2aba29898e974f9fd552 +90f52b2dccf525b9191d836b205ffe966d9a94f6c5800f8f51f51f6c822619e5abdf1257ee523597858032d2e21014ec +831209f8f5257bb3eb452d3ee643d5f063299f8e4bfea91b47fc27453ac49fd0ba3cf9d493c24f2ca10d3c06d7c51cd6 +a5a4db4571f69b0f60fb3e63af37c3c2f99b2add4fc0e5baf1a22de24f456e6146c8dc66a2ecaafeb71dce970083cd68 +b63da69108fad437e48bd5c4fc6f7a06c4274afc904b77e3993db4575d3275fce6cffa1246de1346c10a617074b57c07 +a449448d4156b6b701b1fa6e0fe334d7d5dd758432a0f91d785b4d45fb8a78e29d42631bc22aaa4ea26f8669e531fed7 +aabe43de1350b6831ef03b0eef52c49ffb0ccd6189cce6f87f97c57a510ac0440806700ce2902e2e0b7a57b851405845 +91015f144fe12d5d0b0808c61fa03efe0249058e1829bb18770242f5fb3811e4c8b57ff9cb43deccfc70552e4993892f +8e9c570811ce44133ce3e0a208053acb2493ef18aade57c319276ad532578a60d939ed0bde92f98b0e6a8d8aabd60111 +8b21839b5dc1c9a38515c1076b45cedec245d1c185c0faac1d3d317f71f1bfebba57c2559bcdb413d9d7f0a2b07f3563 +90413bbd162be1b711e9355d83769e6aac52fdfa74802d628ff009325aa174c68f5329ddd552ef93e8fdcb9b03b34af3 +8b6b02e3f9dd1031ebd3df9a30432a3c86e64306062ef00a6d1243620d0cb66dc76f8d0d412eceff877ff8768c2696ce +9894b41d9fc715f8f6addace65451f41dc5ce7b983dd8cb33757b4d7259bef12f144e0077d0b662aa847d5a45f33c563 +a353a9740f6188d73aa4175a6c5f97898a05ed7aae9d2a365f15b91dfa7c28b921fdef0a32d90b6fb82718b33d3ddb8d +984eab8faed87c403c9979f2d2340fb090cc26d00cb4092aeb187c3f4ee1df3f57cb8363f7764073188790b16dfc464b +a5c5ae0ba435fb7f3ddd5ad962358da326239ff236fc3b51bd22e88296236b109951cee1b98f444302badc58d1b5bfbe +880be1006b0156f2788813432f450f613d235f41aba52a6000d2ad310408ad73d86b79f6081aef1e8c51010d404ba670 +937da751aae68f865c7a33fa38d718f20e2a1c65cb18c8e08f8441f0cdc77662789d2793794dd0a427cad30cd0b33f42 +9496fde66c834ff86f205897db12bbf9a9bb78d9ba8b5fb539cd0a2c927cc6b4120c017b0a652750b45edbe5f650e5dd +97a6f409ffeb593e149307a14bc47befb632412d70565c5f13d6b7d032acd2e3ed0f7b6af701b387f11d69ee4a8094d7 +97ed94934263dc0260f4f7513745ed3483cdddb9adb85dc33193c3a8b4d52affaf1ded23b59c34651afbffe80d40dc36 +b2b26378d44f916bcf999db218b9892e06de8075f205c7dafd6d37a252185c2d1b58e2e809c717963d25627e31f068e4 +b8f9fa1fb45fb19a45223f7be06c37d3a3501dd227c3e15999d1c34b605f888123026590697d0ae24d6c421df8112520 +997aa71e3b2e8c780f6855e94453c682bee1356b5ce804619ef14834475511105b1e4d01470fe4e2215dc72182d9909c +ac2cb2a7cf55aaf990cfada0218453853047e813d3f51f5a623d09f4714da79de6592671358a5edf938a67f905b6cb5b +8d8340d0c3081cd30d34f3ff6191e1ff6ad7994b4ebac19e5936f1157ca84e1813228b7605ee226366d6bab1e2bf62a2 +9693b17669086003cb46c75fed26ea83914a54901a145e18c799a777db1df9c9ca6b2ea3ee91e7b0ab848dc89cf77f19 +a6b6b2a6cd8c4922d78c8ba379373b375d66ac6ea04b830a23d5a496cf714a9439d81c865da92d52600aa4e2e43afcf1 +89cb665020abc3f5e11a03c7ba5ec9d890fa9ed2630f1443a8e45a28c32786ed980b5343ffffaea60eeff5b313bc0d66 +b37b989106594221bc6cf33a1a83c3e65ecdef279e90333a9e105b8139dc28384bb2277edd4b77c9e59d15e6afe074c5 +98ce5aee5918d18b2326b30c1ba41669cce20bc7a1d1b585363305fbdea66055164a7ac398ca0f0e670291a3061022eb +b57f472d5f34beb4cf430d7c0f8ac5bd1c0621a284633ed36e6f7804bc2b7847f54b469c7ea163a436510d9e3b32f97e +ae673a6579dbf0504c8fd0c8fc0252d2f7ae8da615a06f4d215c2f8a8f516201f24e5cc42967630c252905e5dbbd6377 +97c1501835a31091a5a83f0546e01c85ee847a0ca52fb3cc0653f6a826e13d25ddc623a5dea139108f7270a1fd7043ea +9376ee667f3834f6c0da4324fdcca5c04712e0649877ee19da79a2d23be24640c38758fce562470ce2134ca34148ffe3 +818af89c40379a10074cfaba6d5968ecf667f1a68a7edaa18e8977ccb34e0829f237c5634fbd079e7f22928b277f1096 +b8e0af0be0a252b28df25d4a509f31878bcddf702af0e5553393c3dfd4a1f1247ad8dc2668bc8dedc9b41f6ad8e71b15 +811667ffb60bc4316e44bd04573503f5b4dc44d1ec824393a699c950e5fa085b146537ddd6a08a3fede7700396a0df7d +ad834cbf850b2f61ce799c4a0f8ab0c57039d4e1113933c50b0c00175171aadee84894d1376cf325bfd434c3deb44315 +a8b7dfcdb40373ba4d55e751ccfb9070554434df9e359fc165284ee3dc35db6fb6055657ecf5a9e9b7b8e2e1abea4375 +b56a5b9fd41c9d3f65532aa58bf71a38fcf07782e1ae0084dc537862fa02e6d66658b19d6f71c39cd5dbfac418da1837 +a935af5ed224b9533b41a7e79f872f6851591da9e9d906050ccd1b2c772a1d6d010c5fc7160c4f8cd7d3aa14c3bcdc26 +a81e580fc98692567b28323fc746f70c3139d989fb6aabf3529504d42d0620f05327e3385c2bd5faea010d60dd5c8bdf +a8b352054cdcde8ddb24989329a249b71498a5593a13edad1e913c795dcad3d24789abca9c7ed1d57efcc9e3156da479 +b0de8a2bd7f93284b2bc700e442f52ada16a22ad8d86329591547411c23fff0333b2ab0c9edf82bf7903ebf69916eed1 +843e9781b653d1a427f3534b2e86add49d308ca247546f9fcf565f9e08df921e4d969e1b8ed83f3f849e98c0f63e39be +84a4098c5dca9f73e827d44025473096101affd7193c40a0307e3215e850e753e9a08e6e74a442d57626ff26df77faac +b463eaaa2f3315b511c22a97fad353014d840a6a95fe0d457d0677e63e571407d7f5268f8775381a5e7adc3b4163eb88 +ad0417edaa16cfddc288eef4173aa7057ca4f81e815541ac588ef5f24b98d56fed6845deb6ae1a9740a28bb1cd8780a7 +9271963b8fb2288a96e07eac13c0543ec41abdc6d978bd7c44ae08251ea49994412b542c77c8208cd71fd8e7852d4a70 +8b68b6db9044d8bafc155d69e0daba95cd59d6afebb085791e999afed4f33a2479c633d31d534ff767b8cd433d591a23 +a6a06a0e433e385437d9996ce823abda9848754aa9cdd25ec8701af35c9ec15df999825669bbc2e17cedb597a96e8eeb +94d414bff8b6b8597634b77a77d1060db8e1af0d0ddfb737a9bf1c66c8430e93a425510af2464bce4a7b29bc66cf325b +b6514049562af1c6fb7d0e8df6987b020f0b7a6e721f4862e36b1ba0e19af19414ede04b346be22d348b50875803d1bf +a42c7fb34f2fbee8aaccd1d86672d0acdf4e6bb083ff0456512d7e1e43be041cc0924322fcd986e6e1bce5d5ecce6f92 +867cbdd169a52440ae0a75d33a28c7d00aa92b4b65aaac5e62aa53a8fc367c08ab8828cc8fa18b6e7d1f908d158e3382 +a6fe0b768fff3e4a6153e59a7b7508eb2ee8165eaf5274d41ac2812bd4563c4ca2b132f0e27ea2f1c98759cc3589b61c +b3eb1dba43d10b9e17ffec8def053fc96f9883bacb49330a089a0ca5b9ab0182e8b5111ad4aa55c1ce1b6f4afa5c70a3 +a1531351098bdfcda566ff4d811301c0305626c77f954a38420c490e7c684f517eb1a4e4bd2c3904a10bac889cba314a +92278d106ad2f27eacdb86bdb1faa0a07a93765bb79dcff191873c52253af83480114b2299ffe5324f9c31d0abbdbbd1 +8900ba95a90c447fb6fa1f528af3d7a378aec25feb0620516b6b97e54b328fc31af42e46a8ad5e6e3029d83a6f2bbe5f +86053d481179c1ac910d5e7b9a5de82794b442f20e854583512ce1f9c3f09e71d1bf97d6700fe776debfe1527ab97a82 +a32a60de492fc4340336416bccbd2591b5e414fca0aead82281212e24490acc01747537b3da783684e27aeb987245cc8 +9820fe8e0338f21797143f368177e3669a1f3894b40ae9fa3b353125f7c8e85cc424dcf89878f2c7667f65db3b1e4165 +934d64711b4348ac5e1395cc6a3215e5643b540f591380d254165486b0ec2a1d0d21c7d2c6310f9e0eed3d08ecf4b57c +b9fd32d589432eddcb66dc30ad78981360915854cc44b2afeb826b5d48a08e377dc91be66f5bf1e783d1a8bb320f7ccb +98c972cf01efff4fc2e485b47572e2d8dde22461d127ef401b71a111b0603203971e3cde40912643affd7341cd27e57a +8db6c1620760063edabd376f4399b6e1355462e04f5c81cdcb3989fdc00f9a466bc85ed899e886c89c149adad69edbad +ad7b7fda0aa6e2aa66a27235ac5cc680aa04b85dce329fc4be84f75c9c961120a3d9e446aa44539aaac8ea203eecb4eb +8ccb01eaf41d816ce69ebd57754859e263530915e775c4e7d9dac37b2457a9099b9ae9b4c6cb09eb5ff246e3c9320c59 +b895b83b5f7ca46e02697dbaa6157df6c7571864c83e504a8c77d965bc2ba97bf9353a71c56a020df64498bd40e30b21 +8018c07a81c522fbc25f2cb14f2321c61b98bd8962ed8eb7d5823dbe5d1958a5ec2fb5622fd0868e991bcb6cae016ea1 +95b16364e94d01b3664812264d7185032722a4afc23bdd33bc16ae87ee61816c741657c37138d9312cebfb5fcfbb3b2d +94a709209990a8b09bfb4b9581ab471aae3a29526eae861108b28edb84aab6d28f1d7a25dddd8150b70af34bee4ca2e4 +ae06c80839c5a13269b984ff4d8a5938c6f4d8d647b1b1daa8cf7f6145340b76a286cd615ec251a65501e6290162da50 +875cbd0694eeb90d3567da9dc7f570d97b02bd9cf17bfa011efdd48f1d580608a3213bff4006603b8b4079fa66bded10 +b27f88c455f025e1cd902097d6a224d76bdf9c9195adee30bef4a0b0411fff980787285896e1943a62271d0aca531446 +8024880cde783cdb2b863e3dd856be92bacc5b2a1347e96e039fe34279ce528560d2df7d4d1624a4595dbafb40529697 +8883d02c2a5c0e026d941c785128d4ac6f7a9de625ea735b7d6ff27a5ba10fa4d6370d450d99a855d919f40d64f86afc +a1beb985c45fdc30ac536f1c385b40b6113ef6fabc2f76d255490fe529468847a776efa674ba8fed72180f07d3f701f1 +ab83bd9b007561695210e3276fde72e507456ba277ad4c348a2aec7a6e9ebdc2277cb4bd0bca73bd79bd2240a1fc4456 +8db27f516153812149854fd6bb1250e843a3ae1c9637df818b08bd016a769d0497ab6087fe3b2fd4080882713607bf46 +b3891dde4e00d60386aeff161b4a0fbc30bb31ee7918ce5fc0b49aac3238a000ced192c9c4c08d90de3a0ba973d7cfd6 +90a2049a15c02e59024a7a1cb0adea97501c60b1c7442fbbe560054c3d69264e69627ac57b7d9be01bef498bb2a60198 +87df67a4bd72444b5faa4f3b067204c4927c869dd3b29ad192d859589a9b2c1d6d35ed68310081e140add254a9463092 +8f80986a8dc8a0d6408ebbcb4f234e76413c11cb0d66067f9436bb232373100f20a4fded60f08dec3525315abfaa8523 +b061e10beb12ba3683688a4ae3a91600d14878ef78a308d01b93e4918efc666450e3f7b0e56283468e218934231df98c +86b9e55f3783d62e381659d3e06699d788b88aab1ff99848db328a83c97d223f602201bf2127c5ecf419752fed0a224d +858d878e29925c87243e010020007f96fa33264e89c8693af12857b362aee3fac2244057e159651c476ebe1dfbd67bcb +8fd47cdef87d7a569ffce806d2c2dad100692d6c53e5f5dfc6e274f897dccadcee30fc6c6e61373961bbc1f3ecbfa698 +892f2822daf3df3a759bef03168c1cb07408df62e024747a788e94d2da325f880bb9c6e136c7f6643f45b021c6ccb654 +8714e37ac24f5a198f219e7c88a92172fc3db129e044e914663ac708d8101851e7c53fce79d32d0e6da74f2ccd1d30ff +ae95e1dbba8b9e2c8dfbe1c202e9ccfd04fa396470035a699b902fbd86d5e6a31732a7c8cae00b9a4f6e51c8d560c7c3 +b0cd058e77498e860fa20c5f8d9bd09bb249add1badf84ba8d1bd49e704b9b4bcd67a5c3d211840a2c8fefab3fea639b +b78e468d3a7da0dd481f333ae56534e2ef97587be2e259a458e25aa37952aed1cc5f835640f812d8052f5bada8f57b12 +835de7965c6b26e7ad1b92eb6f0261d1f376fa12d61eb618d9b342b597c9c117a5a8f6a36269aeea88072b4641e6b5bf +b4d0eb99136b3643468c9c48a20fad62785a60fbdd3c054efac4bd1fa7979b4c9ca6c2c0b18069c0912bea2f19832790 +a00c47315dc0700a850966836a95f3cebfde04dd094bde0742dee77b89a05b5ad655921f86fafd1e902938ff34d4c58d +ab13fa0afaa92229a71ee91efae6d1b15f14b6eacefffb7401d41d0d6db24e24a8dbe8ee19b4680ecb69d2a0cb4e84e7 +aa56c0fb18401210062dbc653df8e3732aa8921a1280e9737e99b26a0100a13a9cba8ad0317a69bba16193362ee0f030 +8b410324a6406b345df0fa25f541ac20b7313fa55832752f70cf4c79f43b0bd3d5b4cdc447e6ba7bca08d0edffa8e29c +893362241ae412d9e5df46506407595c58ffbd7fb1fdaf0694c3432470599291238997abe118bf7737e56a4f5c9dc292 +921618194a756be81cb49d6357cb392b32cc62d96c8ffb7e16d9659a0f226a0436bd378da7b835054dbe0de2c6372ef2 +94a2904f10994928ff5367b777e1430047736fbece33442cf452018bfdeae62e84cd75cf80f8468285e347d504c94111 +b4b81545b767f380bfe10e0fea9c3cc62ca8db40b43c83ffb245259378731298e3eb6c3bdc3a16932f88f5d8a86edc4d +936203c2453ff01c6fc635e4d54320d69e60047d805daae3b75633c2259108497b778f011e5a057249f11b2b888ea76c +b90bf6378d29339443c3f2008b1e2b5f0345f86e393027f14a295e583bf6e6c2b10f54b6dcc42079ff0d356c405b03bb +916913f550d327de2d8d6c7723dcef2e3869efaf95fd963d95c8980b97748c61ad8e2e629cead8577266d93fe39203bd +a033c6f3d5ecbabeb83eb363e54e5faa7ed2d7f4fb771b161762c4f003eac4e1afb236806b784baf2222cad54e2d3cd9 +ab289d4a5771147e6c29ff9ac2bf65d70081ea6c6af2d9b728c3c144574a31b5fd8632af57c18c389aa2cd994938bb0b +9488da2019ff13e290eeac132b491df58b5b7b23c2898ff1a67bffd7e9c9464c39bc8177a57950fd28589e3d9ff9c6c4 +a5abe42b2e0891851440fb2aa6c1d8a86b571bce8b80c8e9e2692e5cb6d45a1b2f055c9fc4c74a7cd292871604129ea9 +90bfef698e83c2ba4dc9304aa01edd274169a978b7154bca518daef394f55857d0d1922ebef3d91fc5ecb3b895d9e0ec +92328f1372b6406ec80786041b6d57018b8507e3881a08727aadfecfdfcfb0824394cbb1150117ac5da5d71b89e895ae +9719751c5f7a65ae2bed8aff7b4b8c34539ff011b259b7ff54f63f9d987b3fbdce5c99534ed561aadaf07bb6e939e208 +a151816774aa9379fccec21cf212429a1c68cf91b055cbb9d931f461a8d5616c693331a11ac5c6fcfbd17d84ee0b44e4 +a72977b1285618a45943ad00f33f37102e2885eccd2f76785254eeca495068fb1d8d49865343e9e8313c6c2c3b2024da +a6f5ad2e023a1585d90625c9f7094f0e8851c79f0eede8ec582ee8e063407cc5b8298e5fdc4c786e4fbbcecaf33e787e +82901e008febcea0c0a14ae21d985a397630e18ee6e346f4a449f23be228e8f338df567d30211a11180b94fbc5204bec +b9b57fdb8d14d1be87a25f89553b3966eb7869e0519ffdf4cc4d51f4cec90d68f7b81cdc0450e04207276e9c63ace721 +a06eabcf43585a001448f3dc30411f3d5b74fd0a695c81eda9981842ba2bb0081d3f5a8360aa18b6d43ef13ea78b293d +926fe48a7e8f07559b7237beff9504476dd97b5b4d67acd01a3633358a6ba4c7abed5c87683a11209aa2ee759888e00e +a716cd3a84a963e2a5a46145b6ef4ebce705de52bf2945c374152a1e41c228a9c4eae0b6d1e222c1eea8b9c13c002177 +8a9b5985df6fb32cdb06ba1591a977545444478f2fe985ed1b10de61c630f0a4693c2185d63f0dc0256b208072c43b17 +a8eab26ae0ebcdf96a59fad1dc2d5e83b94abb2ea1774b607023f9d9e0fe065853b1e2242e794f989a80a47f550c0bd9 +84adbf38164cd04f3d770a7f4b8eae7a5d25b4a803fb63c02b95b71b33e454319c44e07a760d22bf5f58e7e372d09a16 +90f443a3ba1b9129a0bee400b5b29d42e50bb2aa56b0022bbfc3c6f8d69db40299871ec7c1b68421cc89e1af6b13a39a +81c5a94b379eb98c494a8d0067c748ba47e87a2ada0105202ed7651eb4e5111a0cd8569b06ae68d392c4fd74a37833d2 +8f92324b14a1549ee0b186073a26691088e41556d33b54258fc6e0b000e9624156db4e97861a0ec22960e6c47ca8a1dd +8b021cd0fffe055068cc460aec3cc455952e2ac32be5fa060e0d1b6cf30ed15381618f801249e893b1b9f10dd82077b0 +b3e9f0dcb3d6f0b138f589fa54dfb01f849890ab97016372d004aac55103f363a64bc0e606ddf75430f1534a30fc522d +8fdfe64af891db89b25daa859864d479cb7599486bd6f36e593f8f2f839f942261ffc3eed5001a93fde44cbcdc24c583 +a9e4554373c5073e135874e2bacbee69c65308eb0785532fec6a37834e8d0b437b77a2f11cc63c87d7183b82cd9b6bc9 +b4c47daca723ad7193ac5098cad4dcab654186ec5ea5c0fd014a3ac39726be954565a901694ba211820c011fa1c59e18 +8835427e86cdceb4c11cbea331ed724e4e78af15e3bab5be54f6b926bf66b5d99bcc40dbc456d86342c9fa83a033c2d5 +8ea84590a400cedba047c2661378921a42f5ca0421da58c1bcb37bc686a2aed98afab3fa5e6ba3a51029390ef3cdf4d4 +b48551170fc479d69fffb00fae4fba301e92e37cae08f596db6f6489c3b7020edc074f9e8d7465b84e9dcef1b6b3aecc +a6f318b1eaab00836a330710e88bfe400395b3081485f6a212e3cba9463f6fe7864ba4f71e57a411ecdf2bcb4d189f96 +848d5137a39999141a79f4bdf91150796ba36352d8525821bf3bd6e070b352792d79147341b8254dd60fa8c36e9e2618 +a8526f8904b1eac4ae2a25534aa91e8031e9aac7b8f58d8f49897e920c36c0232f4a30aa6eed305deb0f7793c115b267 +b8b6a727c44c37a8388383e959d195d1d0e51a657d4ba360633d219d43c5df645383e2406c25f1d418e72b862c3a6e9b +92e64adf65b42c978f36dd03ab22ba983bfbb61944efccdb45b337ceb486beda99818bf20d32a545503c4572bb0a4983 +9653bb83df66260a0bd059cd4244ef7c661b089e403d26ba777d2090783ff31f963f5d3a9c125b1ad1a1d19134f3fc8d +a74e72355e71ae5eb36dc75191643500ca3e67f18833ee981010e7e7e60a68e1b01b05901eff05014b9ef29aa4829f45 +8b2139a5da14524cf6acc593144db23db424b95b8c7041d8f6c7a14a6725dda1cd09c42bb3ae26a5a3650affaa742800 +a60ddff4300ca44a7c7a00a1f98441ad1438e07c30275bc46551cee1b681926d2c825cc8f90399ee5f36bb9fbd07d3dd +a04e5e9958867a5acc15fdea0d88951cfebd37c657102f6ba1dcdaa5e46cf1c823ad0d98718e88e436f260b770599102 +95e977abeb70d46fe8d7584204770f14c856a77680607304ce58077550152733758e7a8b98b11b378540542b1175fecd +8c9ec93ed35a25ce00d61609e92d567459a45e39922ccd1c64ab512e292787125bd4164c00af4cf89fd3cf9deddcd8bb +819819ad0338250d9c89aceda9e217df12ac54e940c77fb8420575caa3fa78930689d0377ba88f16d38179a807135dc6 +8baafb379d4150ac382b14a64788d819146480d7a1dccd3deef6889686ded375900f5df069843ef14d754ad3d7540401 +ab827236996bb79b447714c6993af941c5ae66248df4d9a6f3650d44b853badb5c0cb67804210e07a7b9d66ca43092f6 +927656c3eac8d2eb575e3daeb77f9605771170c325bee6aeade10c083d42bd8dcbf3bcc3d929ea437001c7cf9a95e2da +af22b212d5ee44fd4197966b9690487c38a119cd6536cfb8c181f38a94610dd9e057f95774047a446504dd96dd11e326 +a44bd94b9e01e3ba36340f2ac2201ecb477495d4f1fb6726a6b439302deabb5a35d237c6a6aeb7e3b0a65649f8656716 +af367aeeae3bba14fbdb05bcc1a521000dd9d37f5c34ae56fb306d3dfda201d0329a8b6e89d98e15825cb3c6bfdb1194 +abcc4fbdea43e50ded9e2fb01464f4e87fb136e960141e8d39214f92794cfab5634f22cd40b18d8c0e501f2307aad23e +920786cbd674348b9853689915dfcab02cce2a4596d117962bce36aadddf4bdd143891e22f2c8015517039a64e8aede3 +8cde63b9bd57cb3ef743f1f3e8250669eed739e5fbd68c500a3cc0c12f93862a69aebcdbc69dd8f476c2eb307f572a53 +b967e65a5f1cd8d5d570f5e87e7e186fba51b9504f8e466392a76d8a971fb91fd9b7565bcc1647f50d7d15e48b93bc95 +8d5a87b25fedf5edd57d870304bfd9081dc78c3e3e3b38b997260a92edac7feccdaf24feb51822d2edc223b70bb4ed5f +b6cd5d340a57f8ec73723c4f3ecd6601620dc8137a3e75a5d3c578bc79a9cae86b379950c644dee2ff99dad780d025c1 +b6f0a8e754b7f52a85a2a2e6512cfd017f7fb0418d19bb318308951c4e242d3c65bbcb9748da9cbc91a738f9ca577332 +a89dcf7d410bccec385400dd96b1cc6af89026a431d0f531aa992cbd7bc8bfd7c5f360bcb665bda1d72efa17bb982551 +97788e7522427a46c4b6258d15623ef7a565712812fa80d001e1de8dc1791392702f3fa3cce5a8cd1c5755625a0ad10a +b5338fb5e137ff625b27c5148298f27ce8f493e2527c5d0facaa49f29cae34580d0d6c3c1074a2e46cd8db3f56004ea9 +8962f006d7b1095dd0dd132ffe7e87e328510c95ad893cf3b2ab21c177c5cf2c27f47d8856f87e9762c547be009d25c0 +87fee9ce9c26aa476e67e0791a809e0a06a8a98facf3faea730d438d3e516cdf75d645fa75c906e4e44ab9237a22c016 +b75ab972e1a1214bab0b38cc3e973d44bb233acda5b4291f5e110b6fb78fdcab93dc63f01168debd898e165f615be1f7 +b5a0fb52bca279d3853761a94b206acaf313df33ae6303d9b71edae90b66fc507adbc60fb11e758888736c81d5d80c0a +849b8f0005010e684701cd3a4e59e8c89e5fec59af6d2de5b6332cde03b865ea84f07f0b80ec3404380b0e148fbd2c24 +96e2b0b6fe78408f9208f809f5c40398100b2dac202c8c5c33c2189560dea868270a598c419871a5a2b67783354f6014 +b234b81f996142d0df2c719760bf996544820a03195a6dc0ff6a72543692f5a369bf63d1f0b477ef2fe7b3234e41f685 +b85e39bcf40da1a12a535740176f4de749a93824079deb5fdaa004f3282fdefaf5275e3418c88c419bd42a3dd2ed2b3b +a27279304b89a18a4e2b443246f2368fb8b15f46a34533179b6bd2ef683f6e98e222b7a32880b39b8fac1afa90133803 +8923c22cf15c9c1964213d725b337ece9ea854775a06f75f232c4859c7142a3942f418354e33066298aedfba3cb27e62 +b109f714311fb9bc431ef57911e2cad6a3949455b9f23255cd7edea35be629e07f845fe53e2b12a32305ee2f4f264f27 +b51e82ae5c7d48050e405897d0053e9ea4b2714d002e88f78c9a307cd50b9c6b3ee7cb86f86527be9d964b01895fab20 +90db256931c7f98bcf3bffff4d496739185e7a20f329ee7bffd4e0850a37739948ec745285703967f4ca50ec370cf68b +a0485ac0445d88dafac56bfba2563b020cfc370f54c1606c89d12cfd8a4d1336d2ba50306e476155a6f5b0e0a1f2d092 +a00754c3462e74bda928da855bbf90f9077db395e32f03cce9b2955546d900b72330d247b7d607b65e130f5b0d883de0 +8547d56727c3ad8b5c8ce622ed9ad86fe8cd78e6e4848c9845914b5063b17330bd10b46d8d3f18f83ca09ecb28d1afb2 +95b937b2a979bce0e159ac75c7d5d659be8599c92305e73e942aab414793364a3ec28c7c1c8491a5750ba84a29828d8d +b011e150f0294e45a0f4c69409999d0c2e602449dbd67ab95e8258466687cd733a0329083a31b03722f4e2580ddc95e9 +924651a733ad5e5d9adadad3ea6a6babb8e455c8d5f2cb5bdc83fa422e7752592190ccedaa827b866861e73506a6968e +a4d5180122f8e31503ae027e54da50f72f5cfb910a6f7309bd882b5cd666f454672591f1f20e461e182a47d03b47052a +ab19ae659c4f73ea3d21895269dbec583c7029955a36469124ebe295027010faab56c4a475973497f28e9a77c03b8fd0 +ae7ea1a803d0f439e91494f8f35fc1167dae23834c0c699ffe65d3da8b09f8df5a53195a99ca7b8558242279e69578fa +b9d63cf0e30f9800101b43b980bcd2f229758e74b21ad5354866b4e684791c08a184330dc316228a0d67fe0210f2bc4d +8c41629744391ddb96dcbbf9cd99b13d36e57d65962e0aeb92ebccf1c4cc769626feb3ec0363def08eceb102b3dd4ad6 +b2848ff24faf9e667a8c19d050a93896e9e75b86595f7b762c7c74ccdfb9db126ae094961fee7f5d1192776c1ac1a524 +af013bc29206743ce934d5887b8d0fb3667c89bda465d2321835a3618513fba6a459dd7566268220ffce7e0c97e22b2c +8bb799e36db1132da8e8b028ea8487dd3266b4628c56dfae4ea275f3c47c78e3d7445ab8d0aaee4cbf42148b3a148175 +ae2b81fd47c038b5195a52ab8431f0d3cab4cf24c4237252d955aad2156adc16dda9d3270157e0bfe5a44022e5c051ef +8e0129213b1698d2ec6df132356805a8633ba79e672e586dfef664ffccca71834253ba14f296da962651fcba2c002622 +a1ae30b500ae77cd9bbb803d737b4a5991cc780618ac22b5cc179efd8fe10afb8c135457f2e7b86ded485ea12eae70e5 +8a39723077b7c0df6e3bf6548afa3910c214ee275951fbe5155a39473be98099626ea14d844630a6fa90292b9594665d +a628386c79b61aa7314b01d9814aeec20c2a66e3deda322a39957e7135c2e52b1da486d1b9cd61c87afb22c1d10f6462 +97867f469b01249820aadd9a54e12d4fdadd4555f2d530450e1f8f6d2dae57360578e2c2c8ba41e3b5950df596537a98 +97f192d0457c217affa5a24267dd16cb4c01de8fefde9df4884e1906d2f22e73382dcee6c7d910bf6430bb03f4a4f1e1 +86d5b5739de8442dc74d0d8dc78e49210fe11bf8c6ff0f0faecbc47b64812d6b28c8afddf6d9c0212f1988451d6ccb1c +8ff3312ce9693cd4a9f4b8e75bd805f65b0790ee43fd9e075fe4cebc87185bdf161335049819f22530f54fed2779a5b9 +8dc41d85548bee5d51941d55752a500bde3c5a8f3b362da4eec307a963968e26605048a111c9166d448b8dddf6f53892 +996bdfd004b534151e309ac925fa5ee7801c9da4f6b4c43e156d1158b134535a2a3956e1255e0dd72ac2af6bddaebcaf +aead652704b788bf4983c8f725c644c327a6e9f6683215f5c826c09f82fd2e40631791f51d14e6aded91fdc018d45501 +991ffab58a82b98ed8fc7b00c3faca153589fe09cebf6a137ad506387a1ca4dba475b0e4a1b9bdad829f1422facaec39 +9652e6c4ae084221d6bad855ec0bc11b5f855c6efba67f644e0902ab790a98861cecc6ce047c68273c3aa7eeb2f4c7d9 +b88b816507aaeea6dc92b861eabdc96988b74d7883f20a4b30ba249158acaff3c50d261742fc9ad2e9eba888a8d59065 +acd028a51e16c07a10d2073b9d03070457ac5f1246365295a1359d015c460b92b4861125fabe6f114de8197045df408d +806d3cd9d02d41c49179fe7dac5b05dcfc9a205a283135d4f008d0771c58e6f963d7ad0f6798606edda718eb5c7ff3ed +b9b71f1657a6b206fc40159a941e127f252a7b324dea864ecd804f48c0ed86da9778a925fb65491204a92bc2a26fef32 +80ed67bd0e74350c875abedc0e07fd42ce7cb926f0f3fb1949c6ac73f2300b5a14a5c6f6ff8aed99d5ea5029bb8e7ae6 +9875f67a7a473714e4dd75ee0c763ddf88101532d9680724b3848fef69e218b04a96b90f88e0f4409aa40b9a21507ecc +b4a2bb1b421e5243e5e7576a0672dc19f9f70315a03f6411c19f76616ffbb70fc5dc0e57fd4ab85e24ea2261b7ce38ab +879723002ce43e6c75ba2246f51436efe3376242beff987d025c3c4476495af32d52a54fad5d9ec329a442b93bcff1ce +a4121efbefd9c3eb143619afa52a916f199c75024908047763b29466cdfc837c2fcc894aca63044c33c41c777e529b5b +895f637b497a9766714a3d9e3c275a1f0c9ddab105bf4c8b7e663f36cd79492022415bb4938c1a4849bda73106ace77c +b119acb8b161ce4384a924645a248a656a831af526cd337d97e08405415b9dd22060849c76b88a4785eb5e7214961759 +802e712f4c0a17009c4be6c1e5ba2ca3b82adcb68793ec81f4489b7985babd8a3873d544de63d5e5de0cb4dc5048c030 +ab111051e4651b910c68ecfdc33f2d99e7bf4182df68cedbdbbcac219a543e04d93ecb2763fe32b40c095c7ca193c331 +855c73ef6afc6bcaab4c1e6388519fd5cbb682f91995bebd558167715db454f38012291beccea8186a3fb7045c685b67 +a29d02ec6d9baf84c19dfd0eb378307703bfafc0744b73335550f3cd1b647275e70215f02d1f4ab82a5df4d4e12dd938 +91510a45b8a50cac982d2db8faf8318352418c3f1c59bc6bc95eab0089d5d3a3a215533c415380e50b7928b9d388ff89 +8286e7a2751ca4e23ea7a15851ad96d2cadf5b47f39f43165dde40d38ddb33f63a07bc00600c22e41d68a66fd8a0fa51 +a413d4e619b63799dd0f42ac57e99628d338b676d52aec2bb0d1bb39155ad9344b50cdfe1fe643ff041f1bc9e2cec833 +85524e5bb43ae58784d7e0966a664717289e541c8fcaff651541718d79a718f040a70aa8daf735f6635dabfc85c00663 +97f0d48a4028ff4266faf1c6997b6ad27404daa50ca4420c00b90f0b3e2d82ef8134d0a04108a74955e61e8dfeac082c +8df6145c6cc39034c2f7331d488b8a411931c8faa25d99c5432831292637fd983d4f6b1a6f55522b4a42a462d63c6845 +98c2060f67a916991b391e67fcf23e5f305112807fe95bdddb8ce6c4084126557e4c5f003afb32e30bc6808b30d4b526 +8964246b3c2b8f7312f0a99647c38ef41daf70d2b99b112412356e680185da6810ab8ee0855ad7409d334173bcc4438f +b56c2c416a7069c14bdb3f2e208c5a6ad5aac1cbe5b1faf99dc89c7141d0259d1c6250be9d9195500c4a41182ad2ec3d +b7864583a4cae3b1083dcdcff7f123d24a69920a57d6594d0b7219e31bf0e236682442b6499a1f6795cfeb4f5f236695 +a064f94139bf1b70d476bde97099631b1284aa6b4d87f16bfc65c075e58b2f1b3c2d057605259f806e545674a1169881 +80d1bc4acf14c0f487cd57c5d6157b7f38917e93cb660f1c25e474fcdcac3c3dfda50f6bcccfd6676bae25c4b6b5014e +8ad9a4976c4e3e282843518149fcf5d454240740f4b91466f6310b7216d23d70b9b47c42870293252f29f092f330967a +914197593d2d99d784c704cad7ecd3f0b9f55dce03fc928d13e1a1034566c4de754f1c2a5ade047b0956415fe40399ec +8d77f5e29c572ec3c0ca39cbae2072ba4102403265b3d8c347a00386da9c0b8688d6e3280c96037c300d57b3545f3773 +abfdf79d935fd4f06a04938d6580a8cbf9735f0d498f49677f26e73d3b34b7075d525afcb4f14ef1632cb375bef7dd55 +a97a8c446e3edc86efac7bda5e2e5d0158c909552a3bf86151df20ece63b8d18b608f477286fb1c7f05605ab7e6a7c2c +8618d946c7fd62486551c35486fa466bdfcdc63c941e4cff5a01fbbe566b7ea9dc763cbe73e2acae063060b619a212a9 +8d03ee468070936004b06acf64b868963f721f37faa09887f8a82c155ad5c5732572a6855b531db58af03b1afe034a18 +8d3247f75966ea63935ef6049f7c889c1651374adb446f49499fc9191dbcde7ea33cbc1f1e2d3d1756b6e69870404643 +afc853c3a3facb4ba0267512b8242327cd88007cef3bf549184ee891b5ddc8c27267bae7700758ad5bc32753ebf55dae +80df863eaea289de5a2101f2288046fdbfaa64f2cf1d6419a0e0eb8c93e3880d3a3fdf4940f7524ea1514eef77fb514e +8434b5888c2b51d12d57da6fb7392fff29393c2e3bfee8e3f9d395e23ddc016f10ebe3e3182d9584fddbd93a6effcefc +b78cbb4c9e80e3808c8f006dc3148a59a9cace55bcbb20dd27597557f931e5df7eb3efd18d880fe63466636701a8925e +acb140e44098414ae513b6ef38480e4f6180c6d5f9d1ca40ae7fbadb8b046829f79c97fe2cc663cbccd5ccf3994180c6 +936cb8dc959e1fc574f6bb31f28b756499532ebb79b2c97ff58b720d1cd50dc24b1c17d3beb853ba76cb8334106ce807 +adda2116d9fab2c214ec10c0b75f7f1d75e0dd01e9c3e295a0a126af0ea2c66373d977f0aefdda2e569c0a25f4921d0e +89a5cefb80c92dcad7653b1545f11701d6312aef392986835d048f39d5bc062cabc8a9501c5439c2b922efc5f04954d0 +b9acb52747ce7f759b9cdc781f54938968c7eeacb27c1a080474e59394a55ae1d5734caf22d80289d3392aab76441e89 +8564f72ce60f15a4225f1a223d757ebd19300e341fd9c1fe5a8ece8776c69c601938fa2d5c21b0935bd2bb593293272b +a5567d7b277c4ebf80e09c7e200c20d6cb27acbaa118c66ef71cbccb33ee3ddce0e0f57b77277ae1db9c66ed6e2d8f30 +b82e9c2d8df1cdd3b2417bf316d53e9f3cb58473c4cb5383f521ef53e0af961ef916e4f6557a6d8b4655ec01415231cd +aa816dfd2814c8a25bd2cbaf66303ee49784df471bac4b3188074ea30816f00f425234454d40d8ad8035aa925d74da36 +9919f384df20faaa2d226b521cab207dd2b62420d25ebbda28c9b2ca76a2a52203b2ad7844c1a25f5c75f005c5a83149 +b24a6aa35c2d0f87e36598b36224c64427cd69642b6f9c1bd478a62c70f8ee69f85028648f6603b4f04fb21355f2afb1 +892e044bdb1276b455eac2204be105e1821f987c2570494b1f32aa09506caba7ed343cd09b1bc126fed5e0fda3d0eaad +af0e01a3ad954dc048de18bc46bb1c4971db2467e839698e4dd05cd1adcb9261013fe9fd0cafb946c0b586f6aad86d4e +ac152f0a9ace425378daf02510eb7923ff1ed2c0f8d1deb918e4efb63655de1ba58c96438e9aa23abdf2431dc771370d +ad8c7419c097709347e2394195924e09617b47ac5c7a84aeb9deab8975f22155de0f70cf20d8a976551b14e3a2683a2b +808f14f67ae801536fb70a5898ab86e50ad35340cffd0648daed2f2c4564c9ad538034b2a179a6a8bfa27e9d93b4cbe0 +80a74ab7ce4769db93cfa695a166db95f0a9c47885ff826ad5d93310f36d6b18b5351c67c858b9837b925e85a1995b63 +95b88c3cdd64401c345828f4e4754b1a88b4875a14c08a668b90acd499b3b858842669ecd73a46c5d9f1de32ec1a0120 +8ddbd770b7b18a5917eb43926fa05004e819f1d1ead05b915269e4a86b53e0633a90559007e59f6705a3769e2126ac56 +ab6db5fc220754f19948bef98844e6e38dd623565d1695e1198040c228ac4fd863c1f168cac1d036bbfb718d9d8dd036 +97bef628e977c069e60c395a17740e0e1bc1828f5607ae7f30ce5a0c95f02b53af2ad062700a75212e462aa22c3c5465 +b68d465e04fd17ca98501e61eccb0ce30401855e98046e0c1debba71c2153d6a7a704aa36a6f12454696e78e87181cdc +a79cfdd048f4181e005bd0fbac0a8424495474956b58ce858d2b700fb0f931c406282bd33bfa25c8991bc528d12a69c1 +843f55fa0a6a0969daf2b48080738f30b269b2e7ec123a799e5b203c0b3b4b956dc95d095bc6550b0013918cdff8a225 +b683cdf2823036827e5b454bfe04af9bec1850d25a7a7a44aee7696b6ff0468b7ed6885a41dde2b8f3ecc4aec880c3d2 +8b500796e82acdc89778e0c0f230f744fb05f762000fee877bcf57e8fb703d212dbc2374887bdc2e7b7a273d83a85798 +ac35a8ee87bafecb1a87f15abc7ccf4109aab4ac91d357821e417f9b1474d196c38cc41cd13667f68d1ffab5e79a6e92 +b6e517739390cfed5b395d33b14bce7cd7aaece57fe79a7eb3cbf150dc10765c3ea9fef7976a21a2243687e6eea38ef6 +b53901eeee26692273365b789f2a60afc9b5f0df229c6d21b07016cf4c0e7985beec748aeca52262f68084393ab038e1 +ac4804f33d8ba2b4854ca3537bd8bf2dda72d4e94ff7ecaaf9bd3b7f098343d74d765471ef80072ae34f860b052cbfb1 +8c6a30a93f1dde18039bbdd1ef294552bf79856e20bce863e4b8dd72d906be3ff22468ff3610e06b5a7d1745dde7ead9 +88f0607fa3b7cefe20a02115572b16fc3222be86bb19e592c86c48afbe7e0dd523492b0c29a3bceb9a20f5538bc3134c +a660b801bbddad725975ddf9a8f606f76ecef831f954be224d6178c368e1c72d346f00c4a4c95c289b62d36f2af323cf +a75b9a6aea9542b698938dcd6cc2f6fe0c43e29f64b2f54aeb05d35fac73d41aa7fd750af4fa9333644aab8db90775b9 +83e1b7129d963d1cd076c3baa5fe422148e939273db173e4d59d1858a7d841eacac7fe817d15ab8f8a493bf46c2045e6 +9060a2e9c24de11f9c70e039b5ffe9e6d32f1ae39f3dda263610df2265d917679e689898e4a8bd84ad34613dca5e3761 +b42fc8b863a2af15e04d1fe6693c09b46007c0b8298973fb4762b45b4590ad7fe0aa758918b2fe5ed1ed0359754fd955 +83e6de7860fb256ecf7b47506a5e557d0fb0aefe57fb513c7dee2bd9604712d08ca26adca7ba9a54b712372a7c585a26 +90586e9cbbf71475ecd3e7b5753b286804dcce61e165502a82b960099e79272de8b7494b8877b54ae838eb5d0f71af2f +b2e4b0d21208f73b7b75e08df80cde20c4578e117d37092a490af82354e2afd3a7dbab46fa2d12fcb731cdaece69c2ba +a010961239bb8809fc7fb4aa08fa30d33a130f9f417ee9ea60f587dcc5ef4e1b7abcdcbf8e848ecdcb7972ef6af46e78 +8f511fd58d1e3403a5eefdc0a4ba6b8af848c7efddbf9575ee84449facde05ae9a24aa41a5725416467f6fbd11369c52 +b24ebbd2d4482eb618cea1ac4fbfd9ed8c46c0988a27259300a7ce5ce1bb256aeca0357828cbbc4cf0dfafbf586040e1 +b3ea29e9cca55250e9b7b9bd854edae40f0f0cc65fe478cd468795d1288cc20d7b34ced33bd1356f1f54a4291faa877d +8a8b20f222d9e65bbde33638033972e7d44c6a310b92a9d9c5273b324c4ad1a94f2a10cbce8300c34dbd9beb618c877d +b2436a9a647dc3f12c550e4ddc5b010e6f9cb3f3504742d377384b625fc38f5b71710a49fb73ffaf95b9856047c98201 +a13f8b77c70621e421be94c7412454adc1937b9e09845c2853ef72cdbe500e5c1bf08e3c8b8d6b8eff4bce5b8dec9213 +b25de8780c80d779e6c2e3c4e839a5a107d55b9cccc3ad7c575f9fe37ef44b35db4c1b58f6114a5f2f9ca11e1eb9c5fa +96ba6ad4358c7a645e5edb07d23836cbd35c47d9a66937d09486570e68da3c8f72a578bd2e14188d3acc17e563a652d7 +a7f55989814051fda73f83b5f1a3d5385cd31dc34baf94b37c208b3eaca008ff696fd7f41e2ecffc2dd586de905bf613 +882d0c7c81e58eb9560349f35c35e4498dcde7af7be8d7974b79d262304c26ab67ffa5ed287bb193d5f0ab46b4096015 +a607158f0c1fd0377a8ee5e9715ac230abf97406c19b233d22f5911ebe716967cc10425546dc44e40c38bd6c2b4bca2e +87e8cde50e5d852d3f073a43d652f7186bac7354612517cfaecd4a1b942f06fef6f14546279c0dc0262e2997b835b2a4 +a1c93acc6db9d5ee426fb4a0b846bb7a7b8d5915bec777a9fe6907246b0beafb8938941c8c79ed6082155f75dbc1e332 +b1e4f61457b86f76cd93eafd7536f72baf239ce5a62bd5a8085a34e90576b1e118e25002d2de49b01d6e9a245ee7d3a2 +a0435fe9a4bd1031ec5973a103ec9396b2ce9fd982f6d9ed780fa80ac06a6e47a0a6eb2daf52df1dc9292db622ee9fa3 +b66d8e8a1717e4bfa42083b6ef4490e090a73168b2912f2111743e089027be0a4945a229ecf5d0b5eec11b23f0e11303 +8eb764f26904eea4f4169be6e75beaa6a39e4eb524625a15a78befe3d8e3cc82692d9b135590c20ed460d6e4ba630ef7 +b7e4aea6bb09829e53fe83e53f49a7a331a6d7bf76e0073d758577e6d6fbe63dab642b23657355cad48896ad8715119c +8f94207982373a99ffa282673f192aa98d0c4461fb77c31dc4549628bd9687a249f1b3c66b1840929341e42516c5c64a +a9c673cb247b13e17fa5e616f0399b7f5c7ad043e143e44ae68855a840870ab3d2aad737ebcf74c2cc9688d17ef3a794 +b02635104dd28c02068985256975c0af783899eb996e37d021d9a35238deeea9e836760db21869be7b6c82aa687ded29 +b33bc0966389710812b5f6698afa3e9c84839a1b85492ba11e6ded26695260abf66be6fb355d12d3a8524966f0f89e0f +a79c0dd09506951c33da3cbc23843fd02d641fc24c640a205e6e8150240372847312b9381fb03c5d301fe4dbee8d0da2 +b74de6f3a2c502b5b658ebe8a9b7edd78afd036f5a2736aa06502863b6865d131b9e3542e72a86fa2e1d2db4927661ed +99e365def1452ff9fb4b9eccd36ff4154d128469ba5bd73e83ae457ab53977cf6fc04a5d05bdcde357ab539e34bd9fe0 +b4f2bfb95abb47c67870aa6ca38ac8f3ae1b1a2bed064b1be7ff90865ea12e4930fcf66429c7ecd1183fae4a01539386 +ae4bde87f36b912e92398bf72e11d5389e93b2de1b277d7ed4b6fb5a9ab9f71a959ec3bcb734c11079440fe42b86fafd +b826459e568efdeeb66688482b67ef5020787275123fd3192f979b6175e3b0ed59e17cb734a0a052bf13f0afc7bd237c +a99dd735f4a7c85cb23dcc7f4835f9ab32026886909aaa95876b98029c37dc4d621726c872d3a9e50403443c958f4029 +99083545034768010988bf8a9f34486c2cd9da27a1d10db3ab86eb69a1dd9c8ee723e7da4ef2aced63c1dbd53ccc52cb +8ac3209349f0142546c714ef7e9d1b094aab5469b8f080c0a37cb0362da5349e108760f272fbba770aa468e48d9a34c4 +af5f48ed74b21e3f2c1430192adb4b804dc873cd7e8f07130c556c30e7b78df0ef5a14b205368848fa9185e5a68dee0d +b8b741b65d68df89443523ba74203226f1e0d13bab073d183662d124e83e76cd318b2bfff09879c04d81b577ac895638 +914abe4282d11176d4f2f08c6f15e6c2d0cde1ab4de00bbe888015c205f51929d97296a0a8d3ca5641f085a29ea89505 +83ec306b2a9a6780efafe799df90b1aebdbff7d47921a136ea8a5648b9708a97231245a1082fea38e47ecafbbe000528 +95d6b58d70b388dfcee4eda0c9805362ccfb60a87603add565b175b2c14ed92999dfdb0d3724ee3e5d30535f282641e9 +97eeb4de607c8306e1d4e494f0d5db126d53fd04983ab5674ec5996b971899e734fa4011f2c889da21154ea1e76dbd2f +84ff21977fbd873ea06bec444d4ec9ff0e3902edc29dfa25f3bed269b3709e3116e99dc06cc3e77f53c53b736bf8fc29 +8ecf483874a040a4a1c293af145094fedf203a5eb37c3e165857e108cce3e1210e0bfc0f26f4ae5e2194024929ba034d +97d9b92b2ef34609d69402167f81bce225ed3a95718a3b403f702b93e96a121a8f7f072d0ff47e8b25164e204d1576bf +ab87c39cca1803b4e84b32e40ff30289e3cbbcfbe16a70f9e025643824752359be1f10c3e5398df402b6fec64d5a3537 +af84ca57e6944332884b5c84750afe0d5950015e127acec161853d55d48fd864c7da8d59cc5aba4ceceac650b813fcc0 +b1d23d98edbe7089ce0a8432e0eb3b427c350fb4bb39eb2aca3c2bef68c432078cb9b4b2c4966255e00e734fa616638b +8e2b5252e0ea96d40835ebfb5693af49946509975682d68651396d6bb1463f09e75fd0afa04ccea49893b5b9c3e77e40 +8db25e762f1d4a89a9a1cbc61c01698e775906bc88a921b2905735457a35df9ab84bae12e1b1b8dafadd50212f1acda1 +b5f7cd163a801770a4034e2b837e00191b0ac63a2b91032ae9a99ec182d748798df48a14644935fabdbac9a43a26749a +998e7232e5906843d6272d4e04f3f00ca41a57e6dcc393c68b5b5899e6d3f23001913a24383ed00955d5ec823dbd3844 +ab2110a5174ae55ebb0a788f753597bd060ee8d6beafc5f7ce25046ea036dba939d67104bba91103d7838b50e36703d1 +a211972a4f6a0303bec6c86f5c23c0d25ab4df0ba25876cbaad66ae010b5a00aa0c5daded85e4326261a17a563508a25 +a49f53496a4041a01e07f2c2cf1e84e2ee726917bb103fd267451b9b7bb1331c0afde85a79a55409bfde27328b2a4745 +934e915c67c7fc47adeabdde49f63f04644fe234672003be2aa0a2454dc8d9288f94293478936a450f2e3f249d395b5b +b6e69e9d6808ff7f60a01b7aea6781495d7a20f5b547852d3f0af727a7434209d3015a9dd04cbe3e272918e32e345508 +b348d3462092b5c6fead7e515e09611438db8d69650876dd3b56226e303252bbeb9e9f3b888fb911445b0c87132a1d0e +8d6510334a905efe5a32001e167f1ba06f9bc4af7ffbf11b7f7bf3c0076b5cca373d8c47e98c1ba8755bb22632bfe0e7 +a2d5200f20985dcd473d119ee97e1c0fafafa0f191185bfed9cac429cef8198d17665dac4f70342eea66e6e4a7370d58 +8dd7eb6b1841b3f33425a158d33a172b79b2dc8a01378e4174e67a1a4c8f4b887f02c7c3a8f354ed9eac718155bcdf37 +b16ca19388642f71afcd9f7007b490d82f83210ac1a989da9d4bf4c419de07af8c048cd301ec7e01b9d06abda7c169d5 +93cb2d847d1a88de8c1c9d5b3c83efd0b7afb3682942bd2c8ab5ef35b33dc31a097a3e181daab8630d4e840b677216dc +a8b648c769e77a7b41c0c689fe2fba9bc585067e004bcb1732cb7b1618e97b317781c36c23a00680fc780b58c301a789 +918c321100d57712866bdae84edf7e42df30a32853af257e0cb4da028842a43b49e775f3cecb85cd817269c728de7319 +a7b0f6ce42e00c519e69b2c78fd9b75a2e7103e5892d3c1afd70c9b5b9e706180a4bf73dbb2d3eed52bfd521103ec5b3 +90041994af3322b010891356afd8115340bd7fd7ba328716fbc4fe458236c8cad8c7564ae473d6091ec3a54bdab524c0 +acb1ac83809573846231f9be2dc5f3e986cc36dd9574a620b1cced45bad0b11ea957ce8c6cbf964a0af916781c574f05 +ac54677dc002698fc4d454c7beb862ad085d0514f92576f3485a44c0cb47afb9db2c085058918a3508f9b3de0137d97c +8dea56e1bfa150e442f8484b2952b116781d08cfa3072d08657cc09b0217276efc4ab6f5fd726bfd826f6976ced8da29 +a2b09e25baf01d4364b5205fa0c4dea84ef8fe03709113b034f88a0f0a502a81bf92c1d4641e2ac9f3a6f4203d3645ee +b95fe37aa351b4292691a9c2e547224c37ec2751a31ecce59810cb2ae0993da6fbe5efe0ab82f164462fa3764b6eb20f +a3498947e91a3a540e86940be664fc82f1e83ff41a0d95eb84b925e820602a41b7393c8b458bd4ebbe574a754586787a +aa2516d3620c832e5728fefdb1af0be30c871cbad4b166a7a4565af676e73bddc2f2f51acc603b3a022056daad2b330e +a9251b56467fb55f64c70729e2ec77a59d7eac79cc0b4b25ee405ac02aea46bf1cbc858bc773934a6d9bea57cb528185 +ae8c0a4ca7ba6bdca8764bac98df0581f00358db904e57867e6ffdf15542e55f7bad2dedac152ef88038b466ed901934 +b0881e27e52cc6a57c4f3f278dffc7f63a9174b68bc867c16d8a151d9cc4d0aeb703d1074d1927faa9ffb43e10912c9a +b67138465d6654ded486d18e682f11a238d6a65d90f23d6b13eb6a1b7471efbac9ada6345dfb13e5432196d2a256829a +944c69a6f1126edd38f6eef60b8a5bd17147ab511e44e8e0a442e87244d8f35236ee0b8d3dac0631f8598f16486a5f74 +995679dbe03dec775da26708cb9200dabcad983825f1ba601eb9395f9da350ca71e8af61dbff4c668fd0eebac7e4e356 +89de362f02dc14de6995d43cdea3c854a0986c605ba5eb5dacf24e3a85983229bc99a2fcf50aba3df59f0fb20daffe29 +84607f0e2d078df22d0866285614f5d78cf7697c94a7d1b5e02b770101ceecbfd53806b377b124a7320d9fed65000b97 +93e3faab60050dac76ab44a29bcd521813e76ec8e4ae22712d77bb489bb49f98f9087acfd6a77016a09a42ddedab2d73 +b7d64a7a35f21747b8e6a874be31ba770c0d13cbd41448411994e8cebb59591295a26bacbf74ee91e248a5b111aacca0 +8dcad429a2b0d66b9eb8c1c3924d7a72979727db6a535526a3518bed2a9532d12aad1c5a778824ca4cb98e3e513f85f8 +980882895faa347bd2fd1dda7b8ee7ed49e69843afe646f677b371eecc7a10e0f4e40bb55f28995a40080df471876816 +89e8e7fb51df79971e2f7bf65783614abbb0d7f3f1b4a15d3f0d160deafa7ed1c446d9a5ae1a77160d4dd94ceed8af13 +93fda8d350392e9c4d4ffe6534f7e7be53f32483d9319093e8436fbb8166a3c01085dc858373e65c7f4d014e0dc2bab7 +897521a87b7ebf7152de5260c0875e3c7df1c53e734c672569219ee6f9bd196c5ecef159b6a1d3b7cd95e91b9b8803ff +b59affa408a0f7bd7930fa3b88750fd043ce672c10a3adeba95a12f23f0dda1793f761a86f7409ce1e6fd3b3b7195381 +b4422ccc12f4fe99c530cda610053af9ffe635b633d52492fd81271d1f6f91b87171d572d5bd0e46ff63e221fb2fc4a5 +a4542cdf3346ee0867c08d630c2aefc57442f1c05c0eba52d223bfdca5e9d0bb80775cff6ce2e28aa2730231fd7b1bb1 +a7d297bb09118b914d286e5d1e87bdf13f7d174b988e38fb5427902e8e8c674072f36b19055a1070abcf357f8668f35b +9213b0ae24b7cb43ae95e25c09fead8bdbac55141694137d67eb5eab5e90a348a13d4d4d2cbc6436fc4f4f9f7334ced2 +8aed71a0d116d832a372b42a0bb92a1980f3edf8189bdbaed7cde89fc0418b3ab21a04f5c6e1d3b8edf73f1f62bd6b15 +a6c47d77d714c285c84c6b9458cbec5e3b191c0502dffd10ce049cf1ea27ddf868ef0cff13a2377289fa6c932b8e4f28 +92f45622ec02483f2c1e07075a6695416d3768c8984856f284f40734346d56cb5b3322f20c2c9f0ef8e58ddc294a309a +af6450d02b79ac9fc79f35655b58fd3619cd5d38c5317564b453f5f2d79d7a030bf767e399fe01b658a72fbd2cac2356 +a3c01fed5240eb8a61ffa8ff4a120dbcebb53b8e19845949c77fb4f9b2c3dd52c7001df6219ad2f76c785a4ee0f64a2a +af3136bfe8f774187bdf87555a1ac505322a956229a285d28bab1c88d4f4d12245af8dff35914a62e90e49f3dce6acb0 +b20e21d28444fc96737958cd951858fda324b924b4d3d08932540fd4b87150f053db6985b96903906ce83dde0578cbb2 +b7978101071268d1f485134b4dfd1e35f89b82c7d99ae91f58b6745f5e0273b7e06f3b23009033ecc3e41b2e9e85219b +9104b7d75245b784187175912cc0ad869e12f1983b98e052710fb33663224362bffd69ceed43e7d4ad7f998c0a699eb7 +a7624cd71b92699ce3fde0e747976ee04ee820032ac45dd27d769edf3b3379a4b8db358e50c9d057c63b5a9b13d76bcd +9354a76f294005de8c59db10e638ae6e8c6d6b86a699d8da93143da8478d36116211c788d8285d8e01ea6647dfcaa1aa +b85935c04cae14af9848db5339ab6420122c041075ec1549314e3c9c5a610d9b794ea3617c50ca7af6b4aec8b06bc7dd +ad6835a62311c84b30ce90e86c91c0f31c4a44bf0a1db65bf331b7cf530cca0488efaac009ab9ed14c1d487da9e88feb +80339f0245cc37a42bd14cd58d2a8d50c554364d3a8485d0520ea6d2c83db3597bf51a858b10c838bfc8b6bc35619638 +b370420ac1a011f6d8f930511b788708ccf2fe23ca7b775b65faa5f5a15c112a4667ed6496ae452baf2204e9ce0dbf09 +8ceab3dadca807a1c8de58ac5788313419c37bc89603692c7a4d96e2311b7fe9e813cc691a7e25a242828cdf98f8bbcd +ac1526ebc6bd4ac92ee1b239f915e494d0279fbd065e4cab1f1b8a1663f67daa89560f6c99bbc3e63fa845520316d2e6 +8240ab0bc36a29d43ec3059c7e6355ff39567e135f93b243145d3ada97fd1c970743819e0d58bd5171967daec144e7a1 +a99743192a6f1967511b2d3038cc73edacb7e85f84b2926d8880d932d2fa12f5215592311a7548494b68a87ec70c93eb +8ffffc31c235997e59ab33c2f79f468399eb52b776fd7968f37a73e41949111957434f2c0a27645ab34c741eb627cd1f +8949d955309415d6d2cf6ee682ccd0427565142c1bfe43b17c38de05cd7185c48549a35b67665a0380f51aef10b62a8e +9614f727a9dac8ecd22b5b81b6e14d34f516db23a1a7d81771ddaa11f516ed04d4e78b78fda5dc9c276a55372f44c4d4 +aa85d3ef157407bd8aa74032f66bc375fddaff90c612470b5ff5d93659f8c3523b2d1b6937b3cc4201c2aa339621180e +86f8fe8bf4c262dc6a04620a848e3844f5e39a2e1700c960f20ee66d4a559a90141ef4e5091d0f32acb1e915af1e0472 +b3af2eb785b00588371beb3b49536b7919a3f2175d4817de5dcbf7fcc20c512852ef0f313327fd0589b10173f77b92e0 +8388703c512eea59190351f3bd2cce83ff8bcb3c5aefc114cccf9e9b3f78200d8034c3ebe60448aaf6c912f0ff8f0cc4 +95d0dbbbf08ec1ed3975fe7dd542be0a05156a2b3db5092825d918a849411ee536ed958201f74a5513e9743674d6658d +8d1a48802f1a2db247e633ddf61d3ef7a2c062c48dda59bf858916e04f56651a7d51e367d6535964ebf3ae6d2b21b421 +971436871bfe868f25247145a55802945409b3150008535b372c949760d7949dd2fdb40d9b96ae7473bc8f6e9b83ecdb +8ca431728ac0f156763090828a7b6d860bf591e5b9dd3bb3b7f3ba0ca74191f9710ee55efd32db7d18eab5b479cee8a4 +81e28f1a506e84c2b9aba1df720cb50e0b597b2c22f98acc34e710c934cc6f97dcaf33d589e845c2c1f6d8716d05ccac +8f43b11d3f00c41d16c9bc9bc0c44227c056bd77de4f1ca9a799418c5601e744f99066bef47da2d9088ae88eb259327c +8d330aa52744c08ef98cc5599eec8b9b4dd18aa01b803f1d1ca0e29b74f1aa2886ed0224390fc377af25852851fbee03 +a06f5b203b67134c685039ec2bdbcc787353e2575ce73a415db24a517c0c31b59d1de89f12b97cbef0219fb6a1e90a20 +9269a5f49bbb8fec1a387b5d105df88a027de615d5ca6afae20fe89b11746f8d23880db78dac238c955fc8bb3de18046 +af5074b3bc0656421c314547b45b5abd3045ca1b17f5e34ba39d8c1f7928a55d4ca5ea9c2ab59a55909b25255233e04e +8e7ee5d733c8e08f3fb7d85f0628de3de6835121672c65374905dc6d19e02fa2df14c13d5e9835dacd609a4df09abd26 +a9b9aaf83d31e879dfb8e73a0708801b4dbdb5d7c8654b27d2c0f5797ebcacc8d00a82143e2060f0917c9d41f1a03de6 +904872aa1c093cb00e1c8e369a3bdae6931c5b1ed705dd3bffba243dc4f42df3e7d7cf70303d513b34d2245743d765cf +8a4d6b3b1d6afe67383c66693f70b397e510be28e3d97dbc8ec543d699b6cbb0e72eb90a7f65e83cf9f7ef50fb18b128 +a914de13916e6a0dc0e0fefecb3a443cca80d83276513b70c22c6e566a2d41acbd33a0e2836ee09abeffd3a4894e437e +b9c408f5f05934b0aefab301ba22f8254c5ebbf5405b6aa788f76e4b328c150b395f441e3566015a0deb3eca89afe9ff +8d32aa2c81b2a8b89f347c2e0b6567b2117ddbb778fda8a3f19004b7f5aa9dd814b9b3ad35f9223715d2447b2d12f159 +8230e8b9c84cada1bf14ea6aa9ecdadd978d893cf5962fee6c7167ed21239210ea491987f2c8f2e8cfea8c140704ca28 +a5d7b6285fea51c6f21d0976a7c3a97baa3d733a201bfaac0994db6c65611d91c5fc0ebc2a7724ee02b371e575573649 +a54f00a9530f6930069f5e3a8b8b1d52ee1def0aad1763e3c609ec07f25410969b43d5943a94c235ed5eb207b33a402e +a8dc6e96399b81397734c61c3a8154e55a670fa25fa5854b3c66734cbb4ec0d8f6ba650ee3c71da3773ffc9e37abf8bd +8841fbfae1af4d400d49f74495f864804f043416c09c64705251d021b3ab7881f134a00b0241e61010617d04979d747d +95acea7ff4861cc969c1d8cc8775c5eae014ad6e2e0e2d0a911dd916c34ae69f53eef779cc24ff1eac18c2b478d3ba2b +a5dce74abcfb8c68031b47364bd9baf71a91db01e45514ab6216f5eb582ef8fe9b06aaa02f17be8b93392d9b19ab9c06 +89e111169e4ae2f4016c07c574a3bdacd8d2f359561fbbdaa3474de9bc24ef8936784dfe6fe0e29a13cac85a3e622b61 +a4c511af6bdf3892939aab651828259e4ef6ebecfdd503ecc14e61001575b313a89e209cb55a77ec19a64d29ada066ef +923c62156fbf3a44926ffb5dc71f7cef602dbe941a98c61f019a27a18a50c16b6135b6099fe04a2e1dc88a6cad989fb7 +afb9191c541b61afa0ef14652e563cc5a557842ce2afea13e21507dde0ebbe6da5233af949c998c00865c79bb3d45ec8 +8a1f0ad65cb2b225931f41dc53547d756111ecbf5bc57c5ee2cc1ffd61b126d0389d311ffe26cf06eaead95af09c5ca3 +9040b20b5ac2e1a9d30abf7a4eea1ec2db8f3077cb2cfc8736b37222d8d3937f5d9f421167086dc5551e9f0bd2522d07 +b6d888b8c6bd448dccaf99c3f690d47f802e134709ce102fb6f6fc68156943c0762be6f386338163e01eed2d1dd5f734 +b94f0e27bbcda793e4a272603b3dcc739d3bf3207798df7319f8dc9d37cbd850e3724bdd30498c929debad971950223c +9769827767be9d7bacba1b687289e0794c6fe630d33c9b607da1f6a65e3f34cb8bd65327d9287c8c5f3c8b5f6d3d133e +aaac72c993aa2356c9a6a030950441de42b2d746bace29865382f0ef54835bc96958b2f00237d805ee6a69ca82117c1b +a2b1f027d80c1b0e79bfc7dd252e095b436fba23a97a1b2b16cdd39fd39a49e06a1ca9a1345c4dbb3d601ffa99f42bdc +b3fa0ad1478ca571e8aa230921f95d81aed7eca00275a51b33aadabd5cb9c530030691d1242a6ff24e2d4cfd72a47203 +a43ed4368e78daad51b9bf1a685b1e1bfe05bed7340d4a00df718133f686690c99198b60031513328fc353c6825a5f2f +965e145711ecf998b01a18843cbb8db6b91ff46f668229281d4ca52236c4d40804ebc54276e9c168d2a2bfc299bcf397 +ae18e6efc6f54c1d9230210ac859c2f19180f31d2e37a94da2983a4264dbb58ad328ab3cbc6884ce4637c8c2390f7fc1 +83a9200486d4d85f5671643b6daf3d0290b2e41520fb7ea7030e7e342d7789023da6a293a3984308b27eb55f879ad99d +b925fb6ca83479355a44abbcdf182bfac8a3c7cce6cfc7962be277ce34460eb837c561257569be3cb28023208dea80dd +9583dd991b62ae4bd5f379ccd3cec72cfae1c08137ddfbacc659a9641e7d5a82083de60005f74fc807bd2acd218d0789 +ae73bc32e9ff5926e1e06c07a3963080881b976c9875777f8e4cf96af91bf41bdbed4bd77e91253b8ec3c15b4a6d3977 +b2a3ea90aa398717ba7d8c46743e4c487b63c5abb140555d8d20e5115df2f70d3c84a2cb9a5e0536b2d93d24f271b38d +91d119d3bf1d34cd839eb69c6de998b78482ab66bc93fa97e31fb9592f36cdfcd673f52366f8c8e8877e313b92d4a2ad +a1907e20120902cf68912cc3046f8806cabbd7673e80218814cb088e080dd93b5dccba395b13e0025f5755c183276c3a +b2e2011df72504065ec4c12cbc2137b95cfcd1355509671feb7b00dbf7f8d500476a49754cb7fb9219cb5cba7c8afe01 +a48589fb7a74a3dfd782cb3503e6294a81dbb6adb412887569f9408e9079371edbd9822388e0b7ec8d3297ba270f53ef +a203909bfe196ac65ed3e6800d577b6ca5c8fe1d40f7f925a43852951e38883f2ffd250a9e16fab3ed3dc1249650247b +997ac293722a8b98f7e819f8e6c2d4c5bd1103b82d489d8b8aabeb905e95450b9b75bd61442cf68cc957212ec1c55617 +9895a3de62395c33509b153b7820bd94fd2b011f0cac135fcf916482f1eda272ecc79f83a61837e99c3a3c4ab2c5c2a2 +98c2ece4d49a64ec8e06407a0585081003bcef88af35210e22eab91169f8f0c044d611494b755e5bd915804b1d857747 +8bc6dd083b36d076ddf0e0bb1bb87cfd059283ddabb3886f02eb7e27f1f0539b2819527b56b5c13436523c4603ac1d12 +85ab8b7a696333c82dd5e179e12b2e127e67d911de609ff9a03cab95cbeedb1f364aa1f2b5e59353e4ba0d177f996151 +a9478e214afa68c395aa2c7daf8ba1627feb71ad6d8bc7339734cdcdd5a42838e032736c28e6251c808d5a4875ef0d06 +8c53f62cf06a35321c8af3871ee4459768d0745ebf48942b9f464206309f42fc7b2c50f196ae1e43b664f0e2e718a23a +8ba80662f6642d8866e832ec8082a4204ebc993fc304c4b794666856de0407620131a18dc053597bb40a3de0bf8aca22 +8c8fac6b911785d1561a985580c03fb2ebc613ae33e486a92638aa7d4493374118d9a6d9d99121e29c68c3d67ee4e3f3 +90f2c793eee07ad90157040b30558bb3b0164e8ddf856389d6742cf5bd1c712e4c6a8e5678da70a8e9e242ec7864117e +954abed8f6d58896b7f6438c9780236c1c83b02d60a29fa7361559e619e5bc9d67b3646ee39ffafe2b3019bb3357fb50 +b79874f757a33085e1e751544de8fe3afbea92e0234f9c00254c2b36115a16ee46f085f22aa66e0c9177e5106f51b03b +aa148b287cf4f60c64f774282b421aae075f0eaa93a45aab4927750f47e2ef0b811d1846bbb15eeb2f293c80a7612e83 +a588d8825e7b0168d45499dcff6faf0dfe1ba4f090fdc7c06d50344960c0121f10ad109b0b9d13b06ef22de5a04eef87 +8f61ec93d14ebfa9c31731f9ef0fb8907505fedc79378e9a3f65c27bed4d74b41e129c97672ce5f567d897befbceec8c +a008218633f1da10efd01c155f7ed739faec902da6dc48e9f19ccbc8d32bb318d71806285cf2003de2c907bbdd4f8b22 +88ad82c66f7085632d7e348d69da84200c53594553acf5432b50dd1e87f410c802dfea91be3cf804e3117ce13103f23e +8498dba17de0318af227a3f9ed86df37a5c33f9a538be9823f8dce4efc3579e8296cb3b7200cee7c5e0bfd9da23a4b69 +b3c0342231dffe4c9bc7d9265597bc8cc4a82e2980ac6d1407108db5b00349dc91d5116fab51cf2802d58f05f653861d +b3f2730455f9bf5a058598bc60f47740117ba51f6a767e1134516a4e42338b513f377027acf8825da5c4d047a62984fd +816360914fbc9d8b865157bfab07aeb7b90bb5a7c5cd64847b1c3184a52266cd3f8f8f3ef99309ba2edc4622304bacc0 +8fd21b2315b44a52d60b39ebc45970a47b9495f42b88217ae057bebcd3ea0e2476c0c3d13de7f72016ae12ae966a008d +b62014485bc217a0fe892ef1aef0e59604ad5a868face7a93f77a70ba3d7413443fbe7a44552a784d8eae1acb1d1c52b +a905822507e431b35f56724f6c8d2e93b0607ed7a4533073a99cce2b7c1c35367382447073a53036dfdb0d04978ccf2a +81672e39c2b31845142963351de3d9cd04c67c806fdfe77467867463dbbd8a9b0e2400ccc55016e57cbedb02d83a0544 +90919c970ec668de8ec48a2a73bb75cb94f0f8380c79a7909fd8084df61ecd631476ddd474b27103c6817c8f3f260db9 +8fbe37dfb04bf1d3029f8070fd988fc5e4b585e61eab6a8b66caf0ffef979d3ed6a662cd99468ce98ec802e985da5fad +950939aabb90b57a3d667f9820880eb0c4fee5c27fe211ce8ecd34663c21b5543c810b3676111d079ac98644c75ee0ae +b06201ec3c3cfdaf864a66af128effee8ec42d25f1e173c1edf9207979fa52c871757000c591d71a9b6cde40f5001a06 +a79054e8febd0450c96ac7a5fd6bf419c4b17a5926f3bc23a8616f0cfbc2849d97470174cd1baa7c739b12615334b6b7 +81c7391b2a1844ed26a84f054b5f03865b442b7a8d614cd44805b5705fe6a356ac182b66a3c8d415132e389efac5f6b2 +825af1563d0fe53925ec9ac0df65d8211b333474e59359bf1bde8861eecd03f2ac74534d34b7e61031227c2fa7a74e1e +b60dd9bf036f1825295cd2014ef1f6d520cf729b4d6cee0b42cb871b60ae539b27c83aa3f96ee3d490ec27ce7e915115 +89ca43d5b7f3622b42df7887572297a7f52d5204d85e2e1ac6e5d7aa7f8aaea5e3a07280477d910db025d17cd2e7373b +b93a2bc9b1b597f0e514fde76ce5bfb6e61eee39cbf1971ea6db38c3ecb055e7913ec8cd07fb0b0ffae3ca345883101c +8d45546bc30266b20c6c59fc4339eb633155aa58f115a8f976d13789eaae20a95b064fedead247c46665cc13ba856663 +aa8eacfe00e8a4d9815de3f7619d9c420629ada6489933ca66a571bf6c044d08b391e0d9eec7d1cbebe8def1e7523f1e +b32fefc59a0d0319ccb1946b351ed70445d78d9fbb536fa710d3162b9659f10288f12d82b32ecc026d55f16cbad55441 +99c7c45c34044c056b24e8f57123ba5e2c2c039e9f038a66899362840cffe021733e078866a8708504cdc35816cb335d +80def162c134540d5ec071b25ccc3eef4efe158be453af41a310b7916c49ec0ce06bb43dfee96b6d77339e11587de448 +b5f2fa4f68f6a26bcb70d8eab62ad73509c08ee7aa622a14b3d16973ffff508ce6f1aff9ced77b8dcfef7319245cf2de +b4d0436019e779c789464716e1741c189e8945dab7f3072720bd9aa89882fa5b085a1755c48da21541f3cd70a41b0a71 +931e798ef672e1472f4f84c727a101e70d77b3a9f0c0803a5220958d6bbeb8aeeb56c769ab472a3d6451249a13a3f56e +918c10a84de268aa8f1ba24b38fe55ff907be07b1e86b4a4adbf305c0d705c1cf5f65ce99e03e11676cedc89f1a4f331 +8e55a8413b823715ccd92daee357cedd797e69a0e78b6fcdacb7318646b9903dfe05e5501f47b3c52e74055b9eb619a4 +8b329bb63e6c985d7d072dff4680b3f8b1217ed20543277386bd30ec25240d9dc378837dcd5cf4fd9548658635f4c537 +8c2be5386052b22986b33dbc63c5afacb6d0095495564ba4aa28fc8c880a3c78242fb083248d788ed928deb1e30a82c2 +83a2b7bdfcbd25d6b059f27218e009ecb5ecc4da68ead885e00216411d8222062ca42f21c4d9cfa19c31522080af677b +9620334d2633e85646b2e2fc48dc6c3f09c64ef1706ed78a3bb6ce1f6b274a727364df71e97531dfdcb392f70f27f536 +b6c84970ec04545121ec3b79376f4e45053c97e8bf2b11922cc2490a429c38735466097ecb81cc9d9692c74d2fb8abc8 +8e55d707dcf265c5ae29a32c27ce66f200fddb724faa5bbf145ef42280ef645fa2f0cc3cfe2db8599b26c83b91e077df +b910b96b763966402bbebd68a32c15a225ec21e1357fa298478c5981a4310e556103fef0c73bd8903e11c4ed2c065647 +a8fd933a0e9fe8c459809bd93b8ce153e2af55df94b61a1490736b19c89469954da8b72dbd072d798fc06fc3d7a3d60a +811b279c113828e114fd82c2070caa7eb089a46c8cabf865f9c77354a77ebebe0c4c6400dda0e66dd017cfc44d76851d +8ed03e91c331afb3ad6e42767e1b3e8d3a35fb831805ff1b5fd3e91878e04027ff5af1165a3ac295f1578faf2c83b581 +95bf53683d64a0621bf1ca6ee17446783f6c535b7a54d6ea57723487a215759a54f886597a55dfdd560424e368ab2759 +a9bea378768fb1d7ba365a16531c51fc1975f1c73caf2a0891da28509805fa84e2a8db7c6ccfbc620e9002317abf174c +b8308250891015deaf851c4e5a4cf4704d104f94064418488d7e3076d49f36240dcf6fdcf83f45fe8a1d97fb02e3db59 +adcda6b63da21f4074f142f8e7f3a2274f624c733e3a4001054a1809711529c61356aa087f73aed877a58ccb41d38d12 +b80e7869239ae26d1da2e6683f064d1dc93cf4a2b66e9439b3ad9b25324e969bf98014760d29e6b8de7ff152ef498d0f +8e9bf968911df3bb5e3a7655e9d8143e91ee87f14464d7ba9c86e1e31b03ab31b91eda121281b79cd974d9ed2657e33e +9007277e8335a43e6bc3c2f5f98c0ba7024a679b7156aeefe964f1a962e5ac82154ac39d1ffbad85a8f2440f3c1e354b +9422b9d670e997b7c919a429499f38e863c69c6a4d2bb28d85e36ae0895c620f68b71e39eba785e3d39a45be91507757 +926094e01132938000d82dd9a571fef5ef104cd25b4015a25e3442af0329e585aaad5472f0e7a69899ba2d6f734b40aa +95552d8057f7e32c24d69e4d6c51c98403f198a20c5be8826254d19cab2f84d5758e2220cea7e38b7c8a7a23178fd564 +8abcf8dcc8488bcc9ab23c51b9e7a0d91dfc7bebe88b7ed370ee68eceba643e939c5eae66a4aa5fe85120751780e351c +a91bf8198f029e6a4cf6f0cc39b629e9aeff1c77b8739e1d5c73d8c1d3fb5c8f6f23e27b435bf10b5b4ec1cf6a7249ed +b932d87ee3a4b81341511f90fe5aa36c571e8b914f25abcc33dd40ca67a3f6444fe9362c1434744e4af18d6e045c54a3 +a8e960c2be9b1d805d387b3ebe2134d421a65f1fd4c1b4cccdce78f9926f139eea78e3afb449b3d6dd19b5d16ace48fe +a7e2f57cce509fe66707eaba9b4c042c1be93fd6034a9b51d1d30c45c4363eac79d54663d525c9873ab0eec0b1cc4ed3 +aa162a31c2078f4b080199debf24494a8dfdfb9d8fc85b198a861b12a629c73128c55a883e4c2de3dfed6e0e1b83eeab +b5a4d075433eaf4115717a84b4dc37f843d44bba0bf820c92ecdedd5afb61be60f7708c8a151a678d9d5c0ae531bffb7 +b56ab96f7a463c0079e05dc766f3a6a31cae5c5044947734ebe0a26e01367c6763cc8de6c2ee2f3b8218f05bef217474 +b60792ac506b901065a8bc0180a86e028fe34b62ceae1ad640c759538ebf3a2ad9c8c927d662deed6f489ff3ff7813c4 +8c8c2cdf075504d12d441a58542e1f8e4bdf92b3ee4775e836b2734c5ec1e3df919b931386417d04489a1dca806c87d2 +8ed78e91e5c4a68894cefc2f7fa71f02e5e12d40f1bb74332139bc7be4d92c24e07d5ece0e82150ed474aa1337af4c18 +87119c22ff8aa31150bde537d863cad661cc5159b12f084cc319224c533f0deb28526ed8568d00a1441e7d8bb4f05673 +83a60ba5a9cccf22cebadf7318b706c9f29abd25db0e2fc1c802965351b53cbf316df72ee3e9b2d3ae7f3c4494cfdff1 +b73b6a9fdd3e7463fbdaabc9a885b7c82201ad867d1bced1c2484300a01cbbb3f1e21afa95d4c7cbb6cb983416b63b90 +b1d89ad16981ff9217708090d4017662d8838f21f3a3296cffe14590b533905fa06a20e40dd497bd291fa4dfd1bfc511 +8abde560083e071a402e3c7bf31930f537f67d2a7bbc734a7480b1b760aa712ebd1cbcb65b00e11e384e980222fe14a9 +89c731d8f31afea8bdc9c32527bdca257f2a840764d40f6e49403b8e75ae51017d505ea4fff91bf28b6f3a1bc65b8bbc +80e9ac8e077e86ad050ee73dfce268a69564ff1b8419e9c236d981fe7a5f0c2bc756e8603ec604b3b9e36da8fe10a49c +b4f1eea0f304898b1323c6382732e6f40e556bfc68af9ce73f6d54e92f5f23cc4f78eb3f43d578d81e7627fb40f092b3 +a0e3a8d1348f8f153e08ac4839232d75d1d6e81b5de184ec4724f8213baf98d3fe739a96f6b39d79a053b628c3a09981 +a6915ba0b52ffe4a381bbb8ff3791d9d3b848bf89b3bacbb2a7d2e5ae21f1353cdc304b3cb6e82416f7e604035c27d7e +b2c4c9cdfdd2fc9a340ba3ade9423344b9f429e8c7e20a8abbf26400376e312f3ae35d1c456be99dfb5c02fc8a36cbfa +9657d57ca0641825a0aa5687f3f87659d893f33aee819bafa5b1ca1db554811c1c844f971e278606e3a2f096defdc67c +a4ad24d0a557704ada24d8e27a15604bca28679e260b2c69ccc8e6cae5499866724b700605a90df7dfb35130756939b9 +b18d9ea6682f73a1f99a9a4fc98c38fcda02c1a18e8c5fc080cf935a2ac877dc5223fca273dcde190b906178d0fd05bc +8ea5fefad0799c885f50ff10d94bd0af5b99b0a446cd1f367ae5ff529cc47e09f3018115f3c0ccac2fa05bb65b84945e +92450d52e6c7d13ebfcdf5674d6761bbae2fc5aabc865d35d031b588c383e0a64cf69a73dc93948632e2b98f74a5ed86 +a356f171a98df4ec5a96d556eaccc6ad34b4238aafcf0e94ece27cdbb491749fc9692e78b84dfe80bdef2914079d34b5 +b918703a4d3507d266414712ba8eb7ad17da07cc5f952b5c62ef130cc6ed1ae3bf01237fc8848c179725bdddd465b301 +ad2b0554570bfc9d97510cf59bc38e10ca54a93649c30ac9919bd0255e43bf525ab11b74f78a51ac0973cd0c5a5dcb54 +a7ecaf4b631d179d32ac1632390d95196a0035e00da6c0e6e13b5c09ae44b15ae6c21538b5a31b73bc5f650ecd979b59 +a37704eb4d728df2a367e59fcb6c26023136230e37f3b8a2f3ceeb1467f5cd30186fc0116f98b64a8146fd2c5903e8d9 +b09373ce92314678299ae10ec1f93c702911beb4115c6b5ba6efbcab9c7afb599f59793912df70a98868bce6545a33dd +b52a878a1393094fd2b93f2d1eccabf2830ab10800ba4cc24dcc7849cd0978733263aef2fcb766a7cb575a7a99383db8 +8dac097e006fda4fb9d6d7ae52adabd9448ebc8d5bd5b38ac0c4ed38ceb510763174f7adfb0b473c38e52147ccab4239 +86b19c41efb949937d74a7875549ee5e997f9fdac7f7198085afda233cf74341a38d0ca3767c76cd35f875b89a35f78c +99f0d927e5ad25cd134f1c70b72631cc6b5cb4ddb86c0642b900464e33d971213a5239dddaf71f7a42f2d6d02a12dcc6 +8355c38806c335d747d4e97f0083fb96585677da18b409a85175ec35dc3f74671817b34203eb18c2f729717ce083ede8 +abb3603adb061a036eae0afa5f23d79c3b62442e0e3bcdeef896f88995585c1105cd3065410368456a4d36b5b0485a83 +9051c5c0011784885187d04749f774b9b4f6bc594b0e4e18226de79dedc4d7aefa3529c3d2c728e180f96f3e204d578b +91888213e7d321d0bfac884edbd5cb756b280753bb5f8bc6acfc208f525757beca24bdf86fc68d3d8736ef176a960b49 +91258bd7ce6e3b7516fe2f5391a368d826da299e0e99b1f82eaa44b62b110ab696adc92debab8ba098a52f38dfb3c5d8 +96e3907340dffa9da3602d3b94bacff7e1bb8649edd3b9bbd06e1bc6781e78f91ababab12c0b9be7c66dfedc7001b66e +9513555688fcfb12ba63952ab36a67b36affdd71f7b843e8eb99ccbd45421698024608233efbdc905eaeb26b334b33af +9913ca9bcf11eeb408da02e4317c5ca0010fb2f4490b282ddb758001c08b438c3b35351a8cbe10b7fffc1293ccd22d4b +85dc2471860ebca88e5a2766161fdd77f926d2a34825d1134a30418f91a741759668e32fd1e37c415d07ab5824338e8a +8b128917e828a0b5eb6fa8ed72b52fae2dfaf74febee69a2e2f87e8df702f0c5bc0fb620c8d1d2a07f35a15ec9c0f5a8 +964c39e7840c130b01bb481ae7bfc92682b0f124c9c383f9dbf3027f2249151925f4faf36905af476a54778d69da3f48 +80671ece658cf850e522d46d25678f934ce6df043f25f8707235125765d40c2eaaf39eda6092f75039b22cb58bf2c29d +ad4bb0e79fdaa340b1347a46b0f64e801c72a89770dda0a6e4bfd35f2df5146fce9934e4baecb1c2671077c771eb8089 +80b3bd3adc6cf198fcd997f8867d2839a2eb28f57390352ec423b8a14cc1f2ab21c6e286505d6a21fb134dcd8d8f11cf +a26d46a6b8a75748895a1d599e7fd120d896340e79813167a400b2fe463452532a4cab419074663fe1d29fa716b76a33 +82b1f3a8a1df29207d7ff020809113ab06080a7f0c631f76ad33f47cdfb6a567143144df97b4ed7f676d929195b04bba +ad96633a3744648ff0a2e4491e8219c9c6ba6e655cb058c36320a8f72cd5f72c00bddf97083d07650ea9ddc005fc1ff4 +91d0783788626c91662359dc3ff36a8bcc6831e3f4114f85c99910256b1d8f88a8612f53c7c417d55581dea486f38926 +84edd9e87ff3d193ebb25f43474c33fe502a1e2100fd3f93fda6520f5e42214cc12e9f8045f99aa2423a0ee35e671854 +b55e06a4b1fc3ff9a5520e0b7c8b5ac11b28385cce78d91ce93b82f1bd7f7afdd4195d0c13a76e80d0ed5a4f12325fa7 +b0b15c7ddede2b81d9c835ecaa887650622e75d0d85f81b8bbec7ef24e9a31a9c9e3de1f382d8c76d878d1b01373f6c8 +b1adb47c20f29784116b80f3670182d01b17612d5d91bd6502b0dcecdcf072541f582aafc5e7dd9a765cad52151684f4 +8efd1018df9c9e9814a9c48f68c168551b999914a6719229f0c5bf0f20a288a2f5ba4a48ba966c5bffb0fbd346a4fcc6 +b34ea2bd3269a4ddb2fbf2514401d2712fc46c22642f3557e3b9c7acbce9b454dcf789573ede9aa14f39605fdd03f8c4 +a9e1428ce24eacfc460aec2e787c053327ba612f50d93510d58b2cb0f13291ca3d16358325ab3e86693fe686e4f526f7 +91eac7361af4c66f725c153da665a3c55aca9ae73ead84ca2662cf736fe6a348a301be1954723206dda4a2120202954b +a6f02db89739c686407825fa7e84000ceedb9bd943e8a0908fef6f0d35dbc33c336072ba65e33e15ecfcd5714d01c2f0 +a25666faa12e843a80365c0fef7d328a480c6e3cb7f224763c11d8cbabd0e7e91a5b647585ee905cc036afca14842bae +b4348576439cd2e48c01cb9cded7cc4a0ea364ab936dd679ddc7d58b48807e7fab070f2f1ea88595b11af4500849026a +a8c6c731e0d0464ef7e4fc1b049065eb4ce100c01e1a376365c636a0b23851022bf55805963bc15eb57434a837e81167 +b0952937b154e3a4c206f96cd96c76ba37624956b0e4d43470bdd97b4af878326b589e3eaee82fc192437123096799a2 +97d07ec31ecc9923192e48d37df2cf08750050fb452dcfbdb350fbc43e146bae3590c5b732b31ebfa1ce5d884ad5ad57 +a69359aebbfe4cbc4d39d178150039fbf284cbc0edc68a6bd635ee3a1c76569a4a575c907fff691b2a4d82a384c2945f +b321c2c0f6b5902ee9056cce7404d858da9a573d27348c1a6bfea29b2746f2aee7abcb6192504e5a583b0caeaba117d7 +a74e738aa6eb4eea58855ae6f422af22812fb388c83aacca5bd5fa4a88d4c01463174a229aea2830c348dd9ab9307854 +94306a3b106bc1644346bc45c05cdc8287811d5c86cad691bde0c65d6a686eb9c0ce79ad91baa4547e5d058ae8bf7310 +b64140fd77a07633e4ca8d60786452311dcdb8ce7095ba51dad8486f57c3bf4e69bced92603f71da992a48ad817ab275 +affe7f4310f1dc68e5e3cd640bedf864f51bfb46bb752063bfc18e95930021f784e509261ff9c560f53000c361b142d1 +b0d2fee222c6f963ba3385547f921a48964da031d737892604f8f2677d4905dbf615046db57eae6c6dd756709ae6932a +81700c66aad7c2e51168e028b0fe086dea75d3b17d93a4dc1f47a6a0f025df0bae1c8c997901837ad859a84197e7bb00 +aa4ac5fdd602f8b79cace18690e67bad557a93d00c0e295074185e8c6b4059a65495d9971685de2fc01d2171ac8b706a +a8becb3a64fdf35d65d2857898dcf8053b5057a73ab8c5bb5324af1a8015cff47efb85dc3eae7364cd5c850b7962bedf +b72ea09bd0b72f8cde3466f359ea69b194ede93dced534efba1b9ebc6f3bd53942fe2965e992e82edb6050cac4ed88dd +85bb8dd7eef023a251fb6f220af54687747f4c91983ff728163c4618ffac40ee6edc29a0aa6d455276bbe017f63757c2 +85a485254a11b4c4a943d9ec509c0dd1cbfc0ff5273a00cf5c9f0babec973efb15348e5d9451b548293d778e3a2b62a5 +b109f3ac809391e772b589c196b013db69a9b2b10ac3898feb70b986973731f30722b573cd0c9324158ec20416825385 +8a4eb579a840d438bed008644f373ea9ba2f28470d50cf1d70af38ba0e17326c948527b1719dd1bd9ac656ebd5aedd10 +a52e9d66ead5ee1e02ce6108e4ded790d8ec83164a0fa275ab1f89a32200726c8e988d66df131df9e62dd80203c13dce +b541cee9febf15d252475507e11d65c4b7819c26cf6d90352f5e8a8f5c63e254eddf22df0c35a7be5b244233e8e4ee5e +8153c297772adf4603c39349142f98cc15baeccaeae10c3230ee87d62255f6814d88d6ed208c368d2c02332426589748 +970dc9782f1828474e9fab7dcdec19aa106725465a5844caed948eef5c9e48199c1b6bc1a637ed7864116927e84bc65a +a975a920624967f4ecc77ea5d9869c434caa64c330024194615a8d0640c5d4d4fb139ea11a0c73a5c6ae6dd3fbf0ab5d +811f0f9e0c12acfb4b9dca359eaef3bed18083bad96188befc036ad3143b121fff4777ca6dc70a835bbc4921bd25f5ff +82341c6ebdb97c8b72910da95c7eebccd1308b6a92999886aab552f0642882d5c7cc60931577d200efd6066530c998dd +860f7162c2f5fd1c0953c6ce75bd8c52eaa48032b914410681b8cc05e00b64130d1f96ec5a52df66a04c78a9f9f42981 +8a578e674875571fe1a0459843495a5ee1d9fb6cd684b244feb9488f999a46f43363938cd0542879ea18ed14fba10a6e +8df217aba4da6781f0f5139aced472025523ed6e17e504511c04b677ca8197488e237d8bb5dff7b6b3898cd5a6393dd5 +b2c9230ad35d7b471d3aee6f771517cf3145ad26200bd6fe9c7cf28120e2945fed402e212d2330a692f97bb9ac4dcf12 +b78b89e29e8b782603b222cc8724eeb83b2d9d56bc02f59a3c899ab76429dc721358b07dcdaf422f59520b7e7ab4fb55 +82682a5617843c4ac8d4efb4c3ce715c76c1da2c3bab1ede387db503f3489c1bfdfc07d9231d96f955df84fd225bc81b +b0f53725cc610e78b8e8a4e6823a2ffe44dd15a9a5bc8151ab7a3787ddd97e1d7f2f0e6efd2876e5f96417157143e3bf +92c5a93233085e2b244519078770c7192af62f3562113abc8902f9d72591eacf52bd15ce78653ab9170d5067606287f8 +a43ef97dcd9b6ad288846bf31fccf78df72f94bc7ad768baf5bf0d5dfa27bd74ffcc6b6c6ed1d1f09e09be3afa5eaedf +817d43bd684a261fb30f709f7926cc4e1a31fd3a1a5e7e53ba4d664856827b340d7867e23d55617ab3514c8a26a7040d +a599e22d3286b32fafaaf79bd5b0c5b72f6bf266ec68948478f055391336d756b58f9afea0167b961fd94234989f0f02 +b70db7d8e8356df2e2070f8d658e560081442f3f3b95e20f4bf30106835d76161101163659d5d12cc0f335fb042dc66e +b8f725b70c957aa3cd6b4bef0d9647393f7c9e0b7343e92439372f0e9aa3ceddd0cb9c30be331742b87c53f2eb030593 +b2fb5e7762f26036e7e966f4454f886758804d1f4c2da17f3d13b0b67ca337f1fd89fd3cc798b07da6e05e8582c9537b +a377f944dccc300921e238ed67989872338137fe57f04cb5a913c787842e08b8a1adcfb4d2200abdc911fc1c766a7092 +b82e98a606071c2a33f2ad44e7ace6d9471d5434500de8307b5d4e0083e3a5cbc67f0609ca8055f0ea0ee7501b9ed916 +8e58f9a04d33a41ace4944615041662dc35057e645f63e127cf0d70f96ac307d33a62ce98f164d6eed8536c1a747dcbe +b5b11388071ffbf57ac47fc195736613b964ebb91cc8e2c17b32646f91d64ea506282b881897fca96c317364d3290de2 +a40ee9b7551133856cfb3904837f9949a9558e59a418898affb78adf1500fd6ef6328fc4422161909aea2c79ad08c14b +81f9eb4ef28aacdb43e11dfc9aa92ba990be4d3c14b484fa677edad3a3fbfeaa859a7f9322b5e95818240d7326215abf +84939b2b6bc859437d1a7a8d6ec9a357c6b716c4b4cc22abc274af872655940cfc72c99f5d0283d90e05191fcdb1c232 +b78a5b74a90a805410b6225fb9576d6d73752520f25cc3fd1edf8ea9f6559d3080f9acaa2246809b6a66879cd2ae446b +8d0a92baa88bf38dce5385ccf15d345b28e2e5d0a2d469e689353d80eaed8e8408933816d70ad752f226c59a0d5b5f0c +a7e15f8a8c1655b7b346c9488cff278c793505379b781b31b273b4bf09b3bdfca1c8ab2334746075d636b2e05859f215 +b70daf14f2adce03c7b92d6aa181f0c507a80a37493d8dd12419d5ed5f943a98099fefb46ac827d6e4efb9b8233c99d6 +8c2480814661744d116fba7355bc6b1914975e44cf0e976d50b6a20092bb1c636b7b44ed3fe8d63b5555ffc89fa759d6 +a6059528a4fed36abb74ab992b22a4f9bf1d05c5de2bfe6837b9af1adfed98bc37ed7481b5a99675d432743021fcfdb3 +b7e19f1b25bc159e5a769811e773c3a8ffe8be8ac77ed0b711540915e5c6e7bafdb407cf9b85c551f67fd621ce8142a5 +a2f66d4f7d16ed3e7ef5fc90b42676c61a98ff18bd26ccce91de03b6a0130c1db17a6bc57be135e410a76d2255b15813 +a139c916927dc3d3fb83598da9217ca64f0ae127215332e9a7ed82be923b89a801c44580d5617297175f9dafb1c4eaf3 +af08e1e1b04ec95366a12d99c80a9a9ac40ac984a575dd0230cdf4eb346a7686da55ef0a276f3356f814af31f9cbf1aa +98840aefe287369221c0721cd7c1b15b1d670c3cbbfda191cdb5434bcad757e59c30ec82b2d8c75947405888d44da435 +b7c61c8d42daf2e278a12d8f6eed76090b71c82275f8b33504aba75d95103840e8acd083e97a5a5aa79897876a68940d +a0264048d2a2061d32eee4f661957ff351e78436bf49ef973c059612874ce9c91970869d011dc13a5b7c754476880a68 +897199a4d8db8aa2db5d9be3d4f4312e41fa0739eb06c62e2e046c4b9be829a447e5d47227e2d96195d3b7b66eb59da6 +b512a9082881f5dc90b02f8bc4f38b133348c2e933813852f6a8e7d8c270c9ce68a5524af7d1d3123e53b2d02a53d465 +80b332469254a96f53c95ec79bb5a8bb1c387d40e58b73d72f84384c696ba0d3c81d6ac90be2979c364c44294e90432e +ab680c2e547ea5cbf95bf813020beb461d50ee4341dea944eb48f6a8584d35682d20186e3b190b849a1ba25625a7f499 +9070581993a0531d6be372d370c2e4ab2ee53f30e04a75ae61ea0fc2c320914506c4d2d4b4487c1f8fa88356fc45c895 +8424303dad6b4051ab633ad27ee51783b2ead61c5a6dae1eb3ed72fc1f36e2a9b1f315504a4bd90f9664091f2f403d4c +82225611eee626556553b9316dab4043aff241a81826a33aebd9864a91e299b765ba1fb43eea2c2047e6b75b6d7fe3de +8a3fb221c616ad55c352dd5e0c09ee892022013d6965aef40d4f277a42e9fa01226fe973cb99aaf6ffe4f4f348fb54d1 +b07c07679aa51713e8a7d7bc304dc15ed5664b66bd371877023f3b110b3927e09e259ef22895c4001421a69c6c013cc6 +83556c76bdac0dd8db6da231b863c335be076e7299802eebc259e0818c369f933a4a4b18e2df8ca07e82f60767b462e0 +a516f659b7915d2f7cd0f0f5ea2491b15f0c84dcb191e7671b28adf7cf14a56d42cfc0da94b3c269b45c535f6eeded49 +80d7cc6f26066f753041b17ff1bd27f6d4b5603a43729d33d596e21a67356db84ca9710158089def425f6afaf3207f9e +b802a47f9009dbd48851209ea1e2739020e717f0ae80671d9f97a0e43de923273f66b7fcc136a064c8467372a5b02d28 +ac92fec1864a8a911633f377df87aab56713876316d48240fefeee49ab97f7406c22e70f4938b5912c5c4e766146b7a5 +89224225b9835d04428b0a74edbff53dee2be285ddd1e5a3a8c37307c0500578155f0c4052e4bc8be04c56862fac099d +b1d3c8492fbf22ea60732745edd3b0163ba5a20d1a3315e3773f2540ee38cf308d42ec72cbb3e3dcea457d1d132c3904 +8bd00e38ec30ee6c44a0e5b222f1f737c9ed2a4bb9225f1741d6334df966318c8a0fd2fbb109557fe8c9479694b8d8dc +a930ce5454efc0b247dc148aff869963fc5c240241d5590415cbd36634801a04d3873d93635911bb9c0c42ecb005cc63 +b83d4f80e9e0fa47b42175df74935ba8aad2e559b80e84478ab1685bc3eb65d51b93e5738d5ca968cc055ca0c552a03c +b3ae21258f98051f13af3878b8103bc541fe6f20b1c3f8fb4689ddb8800b3c25cca9b55f0a4104bdf15dc4d5844abb8c +831ef8684c1cd446c58c59d0152aeade5cc305bca6aa296b92162615f052ba280fe289edd62fda6d9f0667c186445f52 +97bf9659b14f133885916733b7d4ac7e215495953caba970fa259f7bf6b79e661090ec8d79e1c9ce8dfb17e8552f93af +84d5a89cc2332baaaf3d19627a65f4b107f8dd9228a1434b327732f59883bb54fb8ce60d6acd026ed4b0e94e545d1c33 +8e66cb743f95ca5486400b0d89d02e20b98044be1e3a12983ff9fe086179e5a0ebf4dcd5098703191552e9aa660a6de5 +87b4cfb35bacec805f8148786788db84eb8f4bcecdd0570ecb592c705450ce1a90b6d183d37ef58780ede3995be67497 +a72a4fece5478011973afa543f6d8a8ea06a64b241cf7d8bd81fa3740ac2a4cf10e5120abcc1c1101f94da89507a40ca +89dc6001a96adcd2679916f43dd19ea00508c8d5dd6b0090eab7982fd2f3571b62f3029588a0649e73f49124525407ea +8ca75edf1259599e873530eff6151c822a4018e71a340534219ef8641cb6683215891df41d4e3c0ca2560e57a7aa913e +9282d32f868e5ee6f7fc229dda5b94b603476de30cec0a44a30edf396b52dc0ebd472b8f726d4b67d76179fecc1666a1 +afa24704223707db89690bcf9761f07a093f6009ca9fc945e0a8801fc29f9f51292bf95243e466fe736088af36c55ca6 +b51332508ddd9a2610edd2b0ad120272ca342e96c28baae37a2c4f07e689303a46c237712d07e446b1d67c75aa8ce32f +9219249f3799dfa4eb4770ee323f821e559e7406bb11b1f1889286221b22c8b40ccacbd9ac50ea3fa9ed754860bc24f0 +993515270c128ede64fe6f06755259105d0ec74947b7eb05924a375fa5c6d14822f3d7d41dd04fa5df8aa2aa205a1dec +a83be4c2511bae430034ab15b194ac719d7b7041f9c0e321317f513a97db39e97b9ee1df92a1962f265b7a3e98cdd753 +8ac7feaecd26f7b99fda3ed0b8a08bd6dd33ed5ba687c913ec0ffc64bbbefcda6f265072add4d944f2005634601ce68b +b4e3ac6b09299db9e1a469f3a0b2d8d724ee47a417a517bebc4c2ac3efc5cde086b57b9aa4efccdef2bcf8f456d973f6 +9262a24a84fb7b2a84d700f98dcf3fefab8b47293778c20bfc356860cb84e0bf102bae9facd9986d92d1762e0a955836 +97be2041c42bd25e5eb519279163b0857f8bef627492c27b1182f8bf0033769246be5886422cbd2409c08a2615352465 +b0b87d059a00e3effa2e5e4925da913b245785f2932ac3ed364ad19a064d3561b8aa6afea22c951316074f0df179af36 +891644b7b3321b06a2a40cd96c2b8b29d81cde5b48546483fdda439000982a9cbf1f6333fb6c089d39da6492cdfaefe9 +8da9149b7f4783a24240b7b9c7e6df4abf8d699d3834e31ee591489bf4744141ab199c173db64397c1f9bd5f9c862ca1 +8ad7f9fb2742654aa2964fd468e7645436cefd1308b064fd63fdf0d3adb4caf6cfe5426354f6cc284f208b03d6b2d918 +8435e4668f7aeb027100d21e4e0b6ee22b401d21966a3736b95610de86c7e2f2c9ee5d0f901353675eee5ff458dad69e +9010895f045538bd11b47bb8996f27198c8d6cffd3220569e6b7407f68f35c47d1efdbcecbf9b5e241c3c2879a4f6936 +92a9aa443b5ee7bf13b6f43f2d8d8db7f6f33fd4073a606ec5772421a55f464831419726130dd97829a7d4bfeb1ab078 +843f3266560be6dcbe0258c3c7d7e332330e10630c069892954290288eda301e247f479505a8a1bf7e59c99ccafd104f +915bd1dad808f8a568725bd243f80b5476a2999d0ef60ea3ef6e754155bc4121b2b879d01570725b510c5a3f09cd83ef +97250d781815b1825be192714884630e9f564b9bd737d55b8ac79ab48d0fb3ca53bd21ead7b2fa82a05f24083f25645d +81e2d52333391ff2faab39611689a62d6ead77039e8703f4e012d53eea17a4d46f2e3342e44b6edbe73a542b461bda45 +89c9f9fd5f638156b018831c1bb70c91215f4a2f5a73c84b1208bdf6ad652a55df7213336ce12bd910a0e1a726474f95 +92bd02984d090ea7e2f3eb7d36d1e7b9d731b6b047e3cdd4af7cc4ee177415fea7a145205e484b366d84191f06af85c9 +85a86fc61d5d916ccbb219db52953e1495230aaaca63237e9165276405f07ad9644e253ae394f1ccdd231944e7143313 +a2ca5b3fbc9f3530f88c0ed7071ec3d89b272174c366eedb5d15d2b648c65d23c0faa4e92c776357e7c6883a0084d03c +ad171f5badcc99c8ffc9d8b707d792046f86cd0aa478e0e2fbb32fe095f96cd134ca548d1f7713057694dc6b26465315 +96bd15d57da9980870fbadc98c68db76824407dff2700c45b859bb70d98374d4a4ba99e3ed0b0c17f480fe08f16c6b8a +8300bac69ca088c3ff35749b437215e9e35a16393e9dc094f520516ba57a485def7029d30adfc72bca36eeb285c19301 +8a09e20be64f346668fcc7b07fee9c0ea8094c935cbf4f3a4cdbb613d4b936c1edb9256b7c884efb72393d97c0da00e1 +b1f85827ee6f041f93ab174d847a55710824fa131c9ade9561168c3962a25c617475ebc4105eba6e738961a754442bc8 +a131558f92e215969f41b6a57d1e2f424149eea531723821dd4cf8c54325cbe66b002de2c8287de6b41ab4b5c35f060a +81ba492b8956f73557f361a856c6c884ebb300d828287d5699e22e0cfa75c8e77a61616551d0be5178263898c461d6f7 +b2608f44d3c22fac8e13cb59e4ade8b9a98c4eb1ec0959ea400c97eb937ae3f66837e91917057148befade8389af2f6a +a6ff0323b5a18a4becb2cc6b376086b47cb2baffbfd1b0f2229ef2286fb4a34c5cd83a5faed5def7bbad519fcab8a856 +857d879cb9eff22501d883071382832730704bfcc5cd5b07cdce7ab8dc41c565a1eb0e7e4befce8e0e03a4975d3f11ef +a2879a20c0360c516811c490289be7dfbf7dbd41d2f172c9239f99e3d091957e0446854f9d0f753d90384a80feb6fa56 +83518624f33f19f87096a47d7b8e5f2d019b927e935a9021823fac6564c4f2328dcb172e25bb052748191e75ac682bd0 +817ec79132faa4e2950665712b2c503d7fb542aa57b7b36e324f77cda79f8b77bde12314e2df65c5b5296a6bca9bb0b4 +b2abf8fb7c3690816fa133d5b4aa509cd5a6e3257cfeb7513d1408b12371c4d58c44d123ac07360be0d0dd378e5bcf99 +a9fe1e4fb1574c1affac5560939face1af6657f5d6abce08d32fc9d98ef03186dbb2dbb9fd1decd6d8f4e4687afecce9 +89b2f41e51f33c3ca3e44b692e8a6681eb42a7f90b81c9e0a0bc538341df9e2039ee61f26d2ebe9e68df5ed1bccf8cdf +8b35aa7b1d9e2135b35a1d801f6c9f47c08a80e48603f3850b425f64e7fb9860d1adda04f92a1ba22d00dd0a26e781ca +960574978cadedbd4cd9f764bee92f94e08b7af65403de36b21bffc9424bcee845b3b028af2e9e545dd77cf1e69a6a7d +840aa0f34b5b6c39471f54d9e85f1eb946468c4fc01963a9027cd7864df01f73c2e864f1f07aeed4b1b1af72808dfa07 +834464a84a11200e3c60f816044c254a7d9baed64aed45a17325cef7fd62338e0a26da78d199d30ac3411714dc813223 +b4ac6fe2f5059546f4ad9a361426ead33237b6b9030b129bf0122085c85fe4ccb33cf90f5a7f23c5b708a5ac64b487f6 +a12aa9035464795f2a67f3eaba478d5ebc838ed9e997c7dfa241e1ed60a94b367d3f969ccf0ef02028c35215698b309f +ac8d926492ec2bb68c6d8aa9bce49085d3d266f3d5f1f924032b87c42b44e41da7c047eeb01e4618f9d0f123dcaa537d +a5142425825d813ed8ce1849d81aa40b11f1cc3daa89a9f798dd83065c74820b4da6122b3308f528b074531df66e1a5e +87ff55c9f5aae079e7bf24084dd9c6b3bc260727d942d79cbe8dc13341d98525b4ece3ed8169994b56a387642f09134a +88e680f148ef2ecdcfed33b61f9e0224790fddc9069bd6999e9bede1791e761637c0fd60b52990b6c93e6e5429e483ce +94bc20bf5aac6e9f1060d02eacd06c42aeac9a1c5635b15a83985dfb03938ddb4999a822e865635201489c7f75601b29 +849221cab7599f25f0b114df092bd5e8c2430503ae959bef1543a101de0790a78245db6a145e26f40b5f9bcf533219a3 +88b6f2c2e7a7954fad11009d839ce50780921f80292320868d481e38d26aecd80fa607e82219a99532d88cf33b39f562 +b0d82947dc23c0b88b86c321b582c15decdb825ed909a731b42d46bc895009515a3dc646c98dbec7d71b0722df82392e +a2cfb9f7c1a76c8073363c1c3bebe5dc29fa76533caea41046c51ea9bbdc693a121b957cd96be5b6da18704d1865cff7 +8f0ffab9a83355a22683a9d998d1c1089449eb308711eaad4265f05927ec6d0d1ca39217082a0b372e02234e78dbaaad +ab024661e2b2937ad374c8cf2e3669f1dc55558a3a881e9ec4d461f27e0fa92e2bc88230f038bfb051cf2145ca747a07 +b98d9b9ec9eefa56d38cca959ce1aee7b6d4b41a8dbbd34b3f50c0a5f97f84ed2502ded1ce8cdb5895872360d4ba6d61 +851244158b3184a62d2c98d148e2b1102cf0d5500906bbc2deda95acc5e3bc4b4a3344febbb31ce05a56dfee86a74913 +860d9e2cb886bd3620b5d7499d14b415532482569bd45fd76e3e8052d78a73ae4b2b41f139f9cfb136564108cd93c0f3 +8305a052a0fb2bcd41f3aca075c5f7f233bd8f861451d03f3a6e6e31f7d08dd89fe1eb4dd7b238a78b12ddceaad9768c +adb703e4778c7e14fb83541ab00b5fc344108243ec6827c5d9b302ee68321aa569da1718424e6a57979ab7536d5eb43b +b1a754b87b9e21aeb86217ec5b4fadb7535344567f1bd15e88ec12a833fed68e26bfbe03b7709ce24ba6c925ea0a0e07 +8c1e2f6bf820e1653f3b8213e9d959d8649196223c2aab57b7ebda094f4919f88d883bcc6a0cd0be335f26f5a2a9c962 +a082deb9865fe8668e91db0e4fd7fb50fb3fdae3e7bf1217ce0aa6f286a624624cf936d762bb2b6c3fead6826694f846 +a10540ca05fbcccdd0a2a66aabab3b36e9bb525794cbae68bc3dace6116f58942218e9d5e9af10d67b5f6fb6c774fdd4 +b81d22c4ab0ccaf447cc5fc2ff3bd21746617e6773bf43257c0d80331be2e8437b88c9c45309ee46402b38d3d4911caf +84c7c6e924713cab3b149f641dabf63ad5abbc17c1d8ee7802a6630507aa1137f7e034ba1d12ec13f1e31efbab79bf13 +8773b9d236e5fcfa8c32e471b555264692006bf9a869a3c327aed33da22dfbf5780ecea7158904d4d6ac4acfe9789388 +a4c2c1bb7290eb7af2013f7dde78282148593f066b09faf42e61a3fcf81297caa5a00fdbf6b93609c8c5782a0f25341a +a7bfa6e3f273da3dcfac7cb9906bbe9fa4fc2872b184d79813ee273e6cc4d7f37f46164362707a1976f5b6a2c5d7ed1a +8b71502019e4263fcda354a0fd10aaa7da47f4abb7a0c715c7b017e9eea14f2b64009b29b467394668c7ca995adedf82 +ad7460fba7deccc3f9a7d204233de47ce30ffa55e1e164975cdf06480a6108720bc397b93ca8c959df77d44a1e1f05f4 +a5b8df96ccb7b078a3918e74b1b10da21df982538d2c9313f5129b2797c8a6db9ff8707241ff72d3e9d5983397321736 +aa6cfa6386660c01879656da6c4e72497690708bae6c5cd1d088f443cb5bbbe75561d6eec256a72b9728377eb83ef973 +b9699ce7c5c878e44114ab7a598646c6c7616b8e08a9ef8ec291189ef9945c1a538d2abf1ce3b0da0f8eecb303b81b43 +b8d0fd1d278f53c455de92ec4357885fc6648dc5f276930263da7dc885b4a9628a2113e28b66b1e64fd08189427c614f +84ad8d262f6ef5d93e82ff6f4af995148eedf6d8e079124daee9b99f506e2968922eac2c7d4aea741fceb7733f20b2d2 +ab5e30ab54641e3a44450118b8235554e0fcfffdfbe1430ceb3f7ef33325725741995fbbbb0c16f0875aef0f1e0c98ec +80e2cf8bf386ebda46045852751611f2af80eca2e910d9ec5f6e2c7376611534604ceafa639272b3d503b02bd66525a6 +aaac69af8fbb87da1c1b7c1b9e59942887ae839a91f0c1d191c40fe8163d7f1dbe984e4fd33619c73e63abfa7058f1e3 +a6194224ad838ab86e84dc80e9b8abb121ae6c3c7fddc476463d81f14168131e429a9757e18219b3896a667edda2c751 +b68f36aa57aedc7d65752b74761e49127afa65466005a42556230dd608ecc8f5efdb2ce90bb445a8466e1fc780eea8c3 +886c3fa235d6977822846b3d6eccb77f1e2cd8ba3dc04780666cf070cae208b7513dc4525d19a3fb6385cb55f5048e2a +a9801273ef850b99eb28f3dee84ba4c4017c95398730c447efe8c1146b0719f252709d3397ce60509e05da74ed0f373f +a58c2a5dd13e08ffa26a6c5e5eb18bd8f761ab64a711e928e6101512401ef2b1c41f67ba6d0823e16e89395d6b03ebb7 +91318b564ec8b2d8c347ca827d4d3a060272aec585e1acd693b2bafa750565c72fec6a52c73bb3ae964fdaa479700532 +a058db5d76f329c7e6873e80c7b6a088974522390ccaf171896066f0476742fd87a12fe9606c20d80920786a88d42cec +9838e07f9ed8b3fbca701be0ef32a3f90752bbe325aca4eaea5150d99eb2243332745c9e544fd1bb17e7e917202edab9 +85a9ae7dd354f36e73baa5ecf8465d03f0c53b24caf510036b3e796e4764a2bc17f0373013af5b9f1b8973226eb58cd1 +896a4ff4508d069a7da6ef7bed66e1080991daee8b227f3c959b4f47feaf75fd1b9e03d0917b247c2db11e105395d685 +a36d9a6a037bf498dfc0e535f2034e6cd433c7b52e520469811eb2e9f04499a6ce40257d2905300df7d81f38d1bba075 +97aac3c5492aca879b4c06db1834b30b8850a244d29296046a84c637d9580c8521ab4752ef814c96f255a139660d7639 +8552bf592a84ab4b356d01643c90347377ebf1f2b38a8c2e55a3f34537b8c7dcbd62e6776d6c2114f2bc2d4344d1567c +84474ad163db8e590943ccd1dc50b4f444beb8275919b33f53d42cba89831e9d42ce2de52b26f4412e2a0676ce913277 +900799dfaf5eafeb297c7b4f892438bf2a65ce04034d66f8e5cc3836e4eaffe782fba4f4455a0fcab49102a240d1780e +817176415e35ad4a204b9fd5771bae6cc270f6ff050996cec89efbe461b2940ae5dd3c6c7d7e31b1da5285b207efed27 +965e5791c927d47569bc54ec9b4c5305788aecd87a26e402aabeaeccc03480df46f0586ca2e2a9918885cd03332af166 +b96d9ada4b5a04a94807d71726bd557de94fbd44042d7dba40560eebe8658d1da49eba54499360619f3b2c38e8b5ed6a +a07b6d641a43e02e7868f30db4dd5069a2f221b4f122ce9b11eac04abadc4f25f3207f1d2d86c7935b1a3d9992ea9814 +8250d4d8ccac846a4b1a9fa392d9279b5bf2283c8b95d8164c3c0d199fec8849eab85755f2a2a99d584a0407742e3200 +8324cf49f56fc14162f9a9ebda1ebda0388d09d8688f1938aef7dbf9505fc119069efc552f68cc7cd9213f96fda2c6de +a98e6f1e85268dccbe3bf4e92c9f455c58dcb53de1dba3b78589adf2e50e79f8e245f956e0d098eb46f5d3746826c6dd +b103ec12f266b4153d67b54d8fc079357ee342cbe5008adc3e0689a7f788534c4601e60e939731f49e4a1e24fd589f82 +b2d7681e866420413cc98eae67614d383943e3762d5742cb3c57e26157633c20880eea1209feaf68402d5d33dd699708 +99fed0ae4112ec9ed74baac70d202a885aa51cb555a3886b49016744dd4017640dd5dd564998c4d842a9f38f3e004e68 +95c35401314467219c8bfb1ccd1f1eae6ef4fa9e48fbea14f70d5315e67b16c46cd03554471840e4a5030b077d2a3856 +8d029380e0c294400d6b8673a23aed43697cb6460fc1bcf217aca3b47cf240886644ed09521d6a05f6abf56f99722d84 +8ef54d1dc0b84575d3a01ecba8a249739edfd25513714dd4d1941fbde99dbbc392f7eb9fb96690d7052609af23aa57f7 +b8ad2b7af4812417aa8de8f33a26547f84bb84f39501d4b7c484cc8bb54c7e166c849b95240fbe459a4719a6e3bf1651 +9858545de898721d19930d8b360cacc5ce262c8e004867a050f849f7a2f2aba968c28d51f24a9af56aaba23a9ded4349 +94ea5043b70df1db63f9b66b4f9d8082776f721b559f27d37b45e0a84faf47f948d7c4532dfd854a4bac49fb2ec8e69e +a2fd88d7b15e3c2778f6c74470d0f9e1a1f979a4d58bd205361eacadab9973d585a6508e685e640b272d6f8a448eae05 +88defd6bccd55db8ca84e3c8d0fc55a3456b41788f1e209d0aec19c9c70febebf3ae32cacaa1dbbf796d7ddea4b17995 +88b8cde2449d5ee7de2ee2f32e845d27e171a51ef64f1d3d8a5fd7dbb9f898ea70eb7f6410cddfd7b7ae70ea8073cc2e +8e044fff6ec557824866ac76301b6d93ed19b7177aa6baa95046330f5d69b572b59200e3653cf2f2b559455e782e8960 +b5446b4d6741c824885790d2d26258729dc0ba2f469c85a47d38886d933b785a4f38a951d37f3ef4bd5091c03fa3a071 +956c8afa8056e9a71ab2e8be5241ddbb3a8b3cff2110cb0e7389493d9fa45e6c4b769ebef540a952db6dcd8bd55baf64 +925950cae25615246e29d594ebf34fa7d52f78a9867338648158f2131e6eb4dc17e18f9db8a5fdd76d017b3a9798b3a7 +a17ea4b43211ba990270c21562690b3ef154a46c3d669c4674c80bd424cdfa95d8850c8e882b8d06504f929cba3d93af +b315ec723973a138508afc387ef651fd8a8804f93975fc36c2eeb796a304eeb1508518d8703e666a74d14318253f526f +a995742d7433b3f230e622de23cb2d81cac76de54831491cc29768eb4a56da60a5cbd573e1da81fddc359b489a98f85c +adb2e89f0d15294d7118fc06d4fdbd9c51d3ecbcc23c69797e5b8197eea0d6cd1240910cf22fcab4ef1e2dc2dd99da91 +b5ec9f9fcd0b5d176b643df989bb4c4c1c167112373d662fb414875662d1a93160dc0b5cdf540e8a30e5fcbe6cfbbd49 +b1291b53f90aed275df8b540c74a1f9c6f582e16c5df9f5393a453a3e95624ab7552e93d6e2999784e164046e92ef219 +8bc7b7b1a584a12d5ae63d0bbe4dc1b63c9df9c89bdd1095ff4b8e7c822bf8c1994c92310a3644033c7c9689f4b7d2b0 +ad7fc45506a10ca48f991714ecc055cea376c0cbe667f3b40ee8dad8446218835439ae59bccc474cf47b053748ceba6d +b134756828a5f5725c0b95109e09ca450e3834b127163a0aeeb544e63cc0cdcdf66f8ed98c331c7c98758f46af369a84 +94535bf1636be0974b112fcec480ed8eafc529933f3065c40e417e608e43a392206cfde8bb5a87b720263446c90de663 +a4df4f6efbc3701000fb072e5cbed2754b9ef5618386c51ff12f95d281d1b700fea81fc1365f4afc66a7c83bd0228fbf +b0336b3552b721087c7e2194976a9119aee13ebed9f1c3c494353707fffde52d004a712965f460062ec9443620716302 +99a39d1d1ee4283b75fa8c1fa42b6a3836b734be48bdd48050f9b05e48db6354fef509623c6ec8d447d630a9b3352b77 +8e3dc3583d40956f9e784e8bbd0b5e65671d2ff2a7c387b20fcb7da9b969f2d122aaf7f054d450dc611737604548c03a +b5068ec5b7bcb5d8583d51cb25345990f50d1f7b82fe535a6a6b17756355885047916f466ea3ab09eef5516bbf2dda90 +a8284ec1eb1d21e693f31a6c074199ee85d8a8da2167bffab5fe240defa2773971c8437e358a18f7e58d1e2954f57f6f +aa7415639d29081acbaac3e9c6b059d68e8702db3f430b86bb6e220d476fa74841c875e9d471c8a5423c58b6fee3cb54 +8afcfe6f65fa6e07c2cb3e1756c0ef2c589830be96edd50c3c248e3b17f51a4b08ba92ef7eed7991d81667ddfbf2bf7f +83b9c8dec8ca8f9b85f0e36c08c5523cfeafb15a544398e6f93b48b5fc4b15a0bd05c0f176a9c2469664acab8dffb0a8 +82a128a89ea46b9debe5c903b950c0ab30cd7570b979ca911500b5c2cca5c4ee6b2c2fa414b5f28e367f4671ffce60f4 +b79fd0ccd2629a361cd6f9307c02ecd4d1f07e4ee03ce4b542997e055b07a026cbc0ba05fe3da309efc58db2e401a8fe +b190751141093823b4b5324cc26c4f3258552f7893241201f2fca1ae9b1a1d4d4964a9abdde8642cf308ded61ce5ef09 +935fd48b95aa6f9eada0cf9a25a573f0ffe039888b3410788c41d173747bf384c0ec40371bb4383ddcc7d9f2db3d386b +b9affe100d878491ff345636ffd874ce1f27852a92417694afce4163e6a80c78b2f28d78102fd06c3283ef273ad37642 +a877670276d49ec1d16c9f1671e43ade11c0c1a1413755f6b92be9ad56bc283e4bd2ad860367c675d5b32ff567301fc4 +8c660d16464878590761bd1990fd0fc30766e7e49e97b82ec24346937856f43990e45aa8ad37283cb83fa16080d4a818 +ae1412087da5a88f3ccc45b1483096aeb4dcf4f519ff3dbe613f63712f484bdd8b2c98a152a9db54cf1a239ae808f075 +ad83cead97a9c3d26a141604268f8a627a100c3db7e5eefaf55a1787ddc1dd5ffc7544e4947784cb73b90d1729003c8f +97c3140ce435512a509e6ff3150da385fdf9e0883a5dc7cb83d616ec8d0a0014e4e0fa57a4d12c7997cd84e07d49a303 +a353773ff68f1615454555bf658eabdcca40a9c7bced8537ea6fa8d54764fd1f032889e910d2a2a342835513352e2d2e +89e8df0c17a36ffe08149c2ef8b27306d04cdf437135aaeba697abc65e3c8e91bcf1817919a8a826acdbbe7dce79a18a +9928c2da15ac6cb20b15859c22508cfcd452c5643cd22eb84abf5f0a1a694fdefcd8fc329c9b40babc52630743d6b65a +99d837b556f8d13108eef6c26333a183f59383b39958dd807b10590c3d37f62ade6c4a320ca2e70567e0218b0ad5807d +9272da080e4aa18720b634640b01bf1fe506c7c8a89dee8759a53e2ca5cdbbd4a4f3aca54924c46b935362cf1eca066e +b4d39752c882de1c1daf3854202c1d58c2bcf35c882006eb640fe54a97be2655281cdb91c30d1a41c698617c2cf64b01 +8bf827f4a7d47e07374d338a3d8b5c2cc3183015b5a474b64b6086fcf0cdcf4852046c9e34d7917d69caa65a9f80346c +901bffc7db9c9416e06f593a76d14f6d9e5dea1c5f9557bd8c93b9e70aa4782bab3518775c2a5b285739323579f7cf0a +af7e204388568627ca23e517bcf95112ca8afd4c6056b7f2c77c4da4b838c48791191565fd38398587761c8047d11c47 +ab2576b5366e6bd88b347703f9549da7947520d4e9de95d7e49966d98249406ed9270fe69347c7752dad47e42c4ea2f4 +b12e3b228b761dedd99d02928105494ded6d4fea3026d73d65ebffa2e85e2cd75b6d091135d418dd95ac102c22b5ee31 +a20b4a752685d5e31ee7e2353c8a1b9a5265f12bb775004d282a3ecd9deda44831bac1ac5151646428b66909b2a423f5 +91a1d4bc0062a86cc6786a96fd3eb4436d8a4a187b7cbba02190d1cd6ed3c3797d9ae7d6ddc413f1c94a21f62bd04ef5 +977f18da1a5df5cfdd0276f583cfba2b2a0fc6139520664e20068f8dfdde33e29d179abfd722f142448f4677aa47be6c +abc3ece90f0f7b1d80fd917de27ab0d88cca584ef959da520825e54cb5a71336b15f8b348532d08d47a6fa600527ef25 +888d36a2c7cc13a1c1aa338a183a74a1f57713e76cb825f9837f43279ce4741999b76a16928147537bcc20f2e0195b0f +af3f5dfdc2dcfe19de893f385f39f550cb1dab67c2e97f1d5fa735e5ec96d6680066803e8a0eb010dd4399f654195513 +a0fb4e08ff56530a940a86c28830956eb6dec2f020f7faaea7566faf0a4fafe0cffe01480e87763ec22f201be51a6451 +92343c5b107910b203c64a79c93d354f7ee5b7d1e62e56732386776e275285561cb887019cc00d3fdbe3b5d54460bec1 +acfe7df83c4624188a1011ad88c1e1490d31a8a8c8016b40aebcdd7590d9c0793e80d2d7ce6a7048876621c252a06a5e +a7da001dc1e33e0e129c192d469d2bd6e5d2982eb38f3ba78bae0670690c8e70f40e8114a57bd0718c870ca5dd25b648 +a903de5ff97dc83628290d781e206ef9d7c6b6d00cadc5bacffb31dc8935623ab96ade616413cb196a50f533e63641d6 +8f9658d42ad14a60bbf7263f6bd516cfee6b37b91a8f53715d69f718a090ad92484061c2cef999816760a78552fae45b +8c15b72b3d5fcb9ffd377fd67d9dfbdd706593fba9629002639973db12aac987bd1db70250ded31c88e19efff612cdb8 +88a2a4034decd854fb557960194ff3404e239953818a8a891bf72a0b26a8e570a65c4a630884de991ae7452b3234f31a +a09cae5c4c190537bf1dd75bd7bce56f7b799762af865bb9d1ee970f6a133c27cce0dd0f14a0e0516ceac41054e6998f +9760ebb1b40f9a97530c3b940d4ef772a225e5b63bf18283f8e302b9436c5209f6294980fd37058060e429fb7fdc3a56 +adaa9400eb86d857dc591b25dbe3bc8f207b69e77b03cb5ee01f7e4b006b5c8f6ba2b51b5a45687479885708509363de +949efe6b00b3248846747a9ad4a934d6e4255994c2b540a59fbbde395fe96d69bb67908441cfadd8c8bbb561fe52da03 +a19a45504b6b1dc3a0fe0e6a1384734a3dcd5a7cb8fb59eb70e49426c4fc44946547443d558e5719a04884ab3a2811ca +8934c9ee21e8d1435426fd0f64232a0670a7946ec524c054cd4f2cc8b1be9f89cc11002ca8aebae646a2050d91716b10 +b1150ff8ffb34ffdcf7d603348c0aed61e5f90ee0a1b814079fc2a41325c75f2f9ee81542797ede3f947884266a772e0 +86ce8cc7c1f92af68de2bca96ccb732f9b3374dad6657dfd523a95e8a931a0af2a80df74098514a06174406a40c16ba5 +90faabb9ace9e13fd9584932846ab28a618f50958d2ce0d50310a50c3bc6b0da4338288e06e5fcbaa499f24a42c000d5 +af4a935c2d8df73332a16dc6da490075cf93365bd0e53e2374ef397514c30c250bcac569b6df443985cf3720a4534889 +b7f948ee90f394789eb0644d9f5ad0b700c8e44e5e9ed0e49da4cc18483676d25740710b1c15a557965da635f425b62e +a917913091245beed6a997ff7043ecf60c4d655c4db0b1ef1c704fd9b0e1ea1335ce8b9f45d6e120f81805ce31555e30 +a48099da8406399bfb1ba834f6f7d864111d0036969a5cb64089947a63dd9467d3857b605e9f57f5ad5f4ec915088d9b +9784c3f9be42eed354542b1446d734521f8e3f01cd9d495ae98f2e4a3a16767fe2ad909e0def5d9a6267f3fc6a172cd2 +8d9afaa323847a3226ad7d7b60d87322ffcda2e4a8df89f58a076f7972d896588de685a2e155e243bcf9456b0a0d6d1f +994413faf0b843f4ec1842c706c45ea5f24351c68674a27887bc8b182eda756856e507a4e8bbfd937e2c4c581b629ee6 +b3e72d9d1ddaa00c7d22f25462d6e9f2faf55e30d138dce8bb1517eb0b67132db758668aac26164fd934d732633bdea5 +8e95875e338f714e9e293df104f0ad66833bbd7a49d53a4f7f5fd5b18a66a61aa0a0f65cc31d55e0c075e0d3e412cb90 +b980091862b1a9f9334b428eae14bbf1cecb4849e3a5809773b0d071d609727270f6ad97f329eca896c178ce65883db9 +915d7ae5ae780bdba27ba51a9788a8852a15355b569581d1f18f0d94bcdfed2c1ed5a4f58e049e9825cda11f92b2c2d4 +83e581058edf9259d0b06128282327cacbb6afc939578223cbf93544599f799a8dce1fb21d52464f990a877086f42506 +803612a38b6f6efb97941997e101ac1878e192456f8fbddb3359aa7f3023434ed8fa92e60ec8e7b4473b1948850e4311 +864a1bf4ac046161617dde282e44ab3cc1843da01a09ca58aa00ed00eaea9351a07a9ec16d910819e7dcc28b8d2c8ada +922eb142845975d5f6f7dcfee6cac8c299b3730400e6bf82cc0bdd9888de21de9d9f1530640f702c003e1ed63b140cc7 +a7db03c5be647dce1385ebc02f4825a654447fa8c4c8d4b22e635dbdd2b3ccdf219384e49a80cfb1e9e6182b6e4227ed +a167289ff0f0967bbab6479e4a8a6f508b001bbe0d16cad36ab4c105ad44f3f180e39a6694e6cd53bc300fe64dac1e8c +b7766431f6379ce62cba22ab938cdbb1b0c7903dfb43980a417e0ee96c10b86b447241e9dd4722fa716283061b847fb3 +90cda18c5d66f5945c07c8c7dc453dee1370217ccb851bbea32578599aa669b4dd245dd8a9711b27c5df918eadf9746c +ac690cd2af39932874385fbf73c22b5d0162f371c2d818ec8a83761e0a57d2db2fca1d757343e141e1a0348016d5fc44 +abac820f170ae9daa820661f32a603ed81013c6130d1ca1659137d94835e1546c39a2be898b187108662cdcbb99d24fe +b2ea5a5950096772f2b210d9f562f1a4cfacc021c2e3801ac3a935f2120d537471307d27b13d538dcbf877a35ff79a2e +ad94af4d0699cd49ba8ca3f15945bd09f3f7d20c3aa282a3113cdf89f943d7793e59468386b067e3c1d53425dfe84db4 +83788367ec97cc4bbc18241cbed465b19baa76fab51759355d5618067009298c79d0a62a22e2a1e6dc63c7b90f21a4a5 +a3e142d879096d90b1e0a778e726351fa71996466c39ee58a964e6b5a29855123d4a8af47e159027e8e6be0ca93d9955 +860831f8d3edaabd41be5d4d79c94921625252aaec806251fb508e364e39fde8808d38b10d557e487603a1b274c9bc3a +88da39f334bd656a73c414ec17dda532059183664bbbac44eb4686c2601629ef8ff9da992c337a842e3885b684dd0032 +b50addbdf7164e8303f33de5ce854d6f023d39c1c1984b214d9e5fb6f6001cd5bdda816f048a438ff3d696872672f805 +999e58c4c69a912b84561cb09610e415b43832beeb95897eca8c403ef4754f4277754d492eef3673afd4362f50060fc9 +b88ea0f60f8119c5a1fd9294796d387472dfad22442b29659713d1d88e7d854cb7cf5c9ef773627781188626bb2fb573 +a068b3844e9dbcf74b54fd55904d56af754d8ce4c619fead7a07f9bfb9d02118db7c512ccec2489d2a84374ec1d1fb6d +871dee023768636003c799e6f6fd8d31315a4c0da7286345cd64264a016693b3485e0732be1bbd34dd5fa04dfa58a983 +8021e8f508680df12e4a5a1bd49f2d7142df65158b0a7198ffa83abd16053a542fb93ffc33e5279020ba8c6a26feacf2 +b5d3cd64df5bc965228b0bd4ce9e5797c409f7b64a172ba165e44a8e4b38e3d5fabc3e0b9a19afbfe427f887c40a315d +a54fdebbb594bafcefb1a03697711e0091c072e1cc24fb441fefd4e0a0518675a1d7b0966cb8294051d7ec0ac175d0cd +93922202337f72969d6d6e14a29c9c75e0420dfba712029941d1504b9f6f9761d706cbc0652cd09a1aa5d22aec766af1 +9711ebf1c7c7426190d4afd5dd03b014a456bbd9d90ed101623866a280550df26a629dde400c03ee3699f7d827dc0bb9 +b4d686d8bc5c1e822a50124c1cc23c6bc3a1577a3d0b8d4b70d1797418aaa763283c09e8a0d31ae6d4e6115f39e713c4 +a533ea2ac683e4ba07e320501a5d82a1cfc4fa1d65451000c3043f0fdac0a765cc1125d6cc14fe69975f3b346be0fdde +94ee563134fe233a4a48cf1380df55ead2a8ec3bf58313c208659003fb615a71477e5c994dc4dcfb2a8c6f2d0cb27594 +93e97d3f3f70664d0925be7aee3a358e95ae7da394220928ae48da7251e287a6dfbd3e04003a31fab771c874328ae005 +b57440d34615e2e7b1f676f2a8e379e1d961209fe00a0cf6798f42b7c28dbd03172fce689305e5b83e54424bc3f4a47c +97644084c6f7b4162bc098bed781dd3af6e49e7661db510975528f1dea8154f3d87e979bcae90c3df3a7752eb0752889 +a923b27b225b2a6dd5bdc2e3d295b101cac5b629a86c483577e073cea1c7d942c457d7ff66b42fcf33e26c510b180bc2 +86698d3b3873ed3f8ab3269556f03ac8d53c6e2c47e5174ec5d14b3ed5c939750245441c00e2e9bb4d6f604179f255ef +87946826d3aa6c7d53435c78005509b178fdb9befc191c107aee0b48fbe4c88a54cebf1aae08c32c3df103c678bad0ca +860864896c32b5d4cb075176f4755ea87fea6b9cb541c255a83d56c0a4092f92396a3e2b357c71833979b23508865457 +b78fa75d687349e28b4ddfe9e2d32bb6a3be13220b8f3ff1ded712088bd0643da9b72778bcca9e3b103b80097f48bdd0 +8a188b940446598d1f0e8c6d81d3cada34c4c1ae0118ec7e0eacc70d1bced28ae34b99667d5793d9d315a414601c3b22 +842ac6f7dc14191ab6dddffcbc7cb9effba42700a77584aa6a8e17a855cd444c5d138f9d61bf55f43c6ffbcc83f92bc9 +b6742902c3d145a6af9738c01cf9880dd05c85f0d0ef7dbe93c06fdd6493333d218339ebc2a02be1895436a2f734a866 +98bf18488483c627b7181b049d3e6f849fce1f15794de59dcde6e5a9b0d76fd484a46e48822a6a93001d3aa12f48bc6d +8769cac10bda8c53a1c19419ef073a5998f73dcf2ba1b849561615a17cbc0a49bfe3eb4ff8801dd36a22fa34b9a3a7e2 +b45c084d58028fdfae792210fcd183abc4ffddeb4cf52ebf3f8a50e4c4eec2a2758f1241b0920bebcb24b757c778577c +85c1216eec8e1fbc1af9b36b93c5d073a81d5fba86a6daae38748ec1573eacc6bef209e76c87a6efbd7a3f80e11d4c3c +b8007e34bb3f927ec06a050b51e633d7eb9e9a44715d5b39712e69c36177a03cd68391090cc3293098e54f6cf65f6caf +8e85527b27c9152b1ba3fdd532a76a79064ab097570508f233e09978761dfe3012d537411b47d0e4b65265eb32cea2ae +899779f3c31a20b76068ec8d59d97a64d2249588ddfd69dcbaac6bfaee8ce0ff3c5afc4e17c934ae7cd041b760eb555d +a5dac3d8f5fbef018509612e25d179f60d2a62451c76426bf546e9666fcdc73263d34aa6fa7e2bfd4c9947bbf5095eff +896900eeef9be2b2e755128e7b1c436af6fb3984f1e66c444bc15fcf3959013b4902c381f0eab1247f878a6ebd1f4ee0 +8cb17f4b0af2e9b2cbb56f46e6a5d6874ea0daf147aae77303020b4e592ddc92e0dd058def7da96258b3a68b223bf22d +a1b6d3f09a9fa7ecc021ab7c5396541895da6e9bf1f9a156c08fc6f2b815a57f18c337ccfe540b62d79e0d261facb2be +ae70888811434ef93da60aeee44f113510069fd21161e5bb787295492eb8df85103794663fc9305f04adcbcf11ff0c5e +a84bbc8624100acfae080ba8cfb48fd4d0229a60b62d070bd08fade709efc6914dc232d3f7bed76a59204f9252321aad +aea47d54652abd8ca213cfc623c8e30780f37b095b59ac4795252a29c2b6bc703a5203acff8831314478b8ee8771d4d7 +8dd438eb8be14935f759aa93021c2b24e1d588f7a162c42c90ec3a647b0ff857f60e24c0a8953eb7bb04e04be70f11ce +922b07b5469680a10e7532766e099896f4dc3d70c522d8add18f5f7765d4ddb840df109146607b51ceddd2189fa7b9c0 +83ef6ebd0ae6c569d580093e8b0b78daa964760556272d202d343e824c38eccb424262e5b7809d3c586f9e2e9c5c5f22 +97f98bd357db6e093e967fe180cf67ed09fa711580a5ad48f07cf095b2e8fabbe6319f97d1f15d62c0ec2227569d8dbf +a1953a4a22fe6c2beaf2a5e39666b0eb53018af6976e3a7aab5515550ff2efa89400605a43fb2c4ac1e51961dbd271d8 +a5cbd67f4c0bc98e20aa74c09e6f5fb6f42c08e59aaa477b4b4e61434c8884bc14f17cf11faecf46dc4b6c055affbad2 +87d96818f2c4f12fd7705cf4060a97bd28037c5ac0f0cc38f71189ec49361e438ce863e6617651977708094d5336d1da +85e7c2daae5fe59f8a1541c94df50402a671a17dbb8838113fa4b7aaff6114cf2bb5969410cf21e6a162857f2f7a83a8 +a19575083e1731bb04bb4a49414e97aaadb36d883aa993d1f6847db50007315444814740e67e10177a14e0e074fd4c7d +a00ebfb5bcc3a6da835078189038a1e56b7dab6be74332b5ff7440e53b0f9e1eb9973effecbbf37000021fcf50c7c1ff +8969d7943abd3b1375fdfc7d6124dde82b0f7193068ed6ec83bcf908734daf3487a6a30f7b322e54a4818ae5f86d91c0 +b959c8d210fa43af9b20d1fe0ea8c4921280eb4544ef6ea913309ff9d61c9327096707e84dc1662960519be8e7d080a4 +9011d8ac651c42e0cb03931a9e960f58e02524c6b666047525e3b9097e9f35fb2b4b278efcce2bd5ad463c6d7fd56694 +937e3b22ed0fcdbd9ea5a1b97b84bbe86b7f5b2de3866a930611112f2217f4ee7d9822c4ab1253823f77bceeae0c8e10 +828997e5d121f4c305e018a0a0ba338bd6a34a7b4dc3c5ceab098ee57490311c130e2c045b9238a83908d07098d9fc32 +8d114808eac0f2e1a942d80dad16756ec24f0276763cd6771acb6049472e05a9bb1d3bbd5957f092936b415d25c746b0 +a063c5c26267ae12887387cbebbe51fd31bc604630b3a6e8e177e71d4f26263be89112cd12d139dd4c39f55f0e496be0 +ab1e1582c8d67196d10f969eeb44e6e16214f1316aa4a2a821f65ba5834326da6cba04373eabfd3b3072e79e5c9717e6 +a17b1dbaa11d41457e71a9d45d032448091df7a006c1a7836557923ab1a8d7290ec92a7a02b7e2a29fcea8f8e374c096 +a1ed7198da3591771c7c6802a1d547cf4fcd055ca9010756d2a89a49a3581dfe9886e02ee08c4a2f00b2688d0600509a +af09aa60c0a185e19b3d99ffdc8c6196d8806169086c8ff577bf3801c8ab371e74165ba0f7329981e9252bfe965be617 +98c04cc8bb26ffce187fa0051d068977c8f09303a08a575175072744e0a5fb61191b1769f663a426c30d405515329986 +a542bf1c9c3262d488ea896f973d62923be982e572172e2461e0146190f2a531f62acd44a5e955a9f1e242b3e46d63ae +aef7b7f30efd50e4a66c87482386f39f095bff6108e68f74fd3bb92156c71c75757912b111060cdee46a6b3452eed657 +8afe1e0ccd00079702f16ab364a23bbbd3da1889d07c4f8cb04fd994bf9353216360dbd364492932bfe20b8b69ae8028 +9896c690999db3c08cd7b25efb1b912c3e0f976db98a3e830f086aef93222d06ce570a7b2babcd7c81d8f9955169669c +ac7bcab6a281468907ef1ea8a6c1cd624159c88839131bef6aa0c22f331fc87ec6128a2c2a333fb79df549e4587e1a12 +987935c08a30b099d19f96901315a2e60591baf898581c40bf5eddcda806ff24a4536e30ed1e6c0b128a83fc77b6e81d +a0a6945bbede3bb09a4a09ef27baa20619d3e15af5673b9350601bcebe952597c989870746cf75767ffb73b32c6c9c6f +b0f5590079f0a0302b08a0cc1b7a5f39cc6900c2a5cdc7baa333d8328a731b2df5dbb67e27a154d3c44ed1a795fc4adb +a7294bdeea210e528f277f3d50e89e6d79950494478998181ecb38de675020130256f2f2a075899170be964d478458b0 +8ab3041b895a631869b439d5599a66facba919226ca9b39d915f19d59f9fc82393ea781377e9bd3bcc5a310e41376914 +8da399b59151fd48b2579948bb82698e3c9804d70ec7d6f3cc7e82901f9f2de5ee850349a7d6f43e5e9ebd47bd78620f +80e8c32de83d1083916d768b11a982955614a345d26d85b457f2280ff6c52bb776958add7c1c8878f7d520d815b8e014 +81bbec7bd99d2917d2dcd8a288722fb33ad5a4bf5416fba8609fa215fb80e0f873535349e7dc287f892aa56eb9e39c4a +9665796fe04c8519206fba58496bc84a8b9113e7ea8e152b65f7f732e88beea271dc97b1ea420dbc8257cc4b18a77463 +a97e342aaaf693ddc87e02790278e4bb50117af4413cd703bdf3b7cad2d1facf31fde1303b43ab2e0265467474f97a8a +925549ebebed348886e37773b05cd8ad04906eca4536bfed951d1ee41b3d362ddc6e1a302c21ff3a2d1e70e95117922c +818fdf74d7903502101551bbf48d3c7819786b04b192d9e94362d2fcb85760d8b6f45165a5443aa5221bef400525ddb4 +a9d29de7e8fd31b59f4a087168d062a478b1329cd3c81c31e56de4fb40de7a5be9a5269ef0be452c487443a0b097dd50 +a85286ad573db4c9aa56221135da1e31d742e0f6ff01d6b159086d7258f78b08dad55ec8eb5c91ee9d3404b2eeb67e1e +92a79b37db5e777f9ebbebde24a95430a199e866e56597c7d0b0e7fb54c7b092c2f6cf61fb24470ddf250cf609898281 +8d79f5ca67ed67d52c82949af342a9fc60fb793c47c76d84b4863c550796fcae2dd59e285897c6fb96fe31cee1efa62c +8ad2e0bda03415ab86324992bb62dfa3612d2d003765bcad1468087c27971d08bdbae5252681f0115a184f4885d444e4 +a08815af979286538c31b4aa5ec805053790af1ca58a8c4341be51136d094a8a05e569d876a079033298ad355ccb7ca8 +b96c2978d0165d619d08281d295e90df78bc2375d0afbc3142ebff9c2cd4b0f0aa97a9a0e3740bc4dce0ff8a9fac8252 +b7752cd0e582f35ab0d0036ca9c0a9fe893a6ad325164d78d865a604a85d3d23729e0362553e8b8a3d51816beeaa30cf +99cef1fafc29e7adfe247c753c475ad4bda7a5f9558b79c86e8a65968ede67adb38dc30071925c9d66a13860027a6735 +b9f6c65af178c791b6137d71980651fb09cb5b42f268999c728c6e129985a9c7d77b3dc3b50751bd29ec9ee0b3111dfc +8d73ae61fff5be883a281782698075c5650083f00399992688738856d76d159803be0059fbd9dec48f4f0432f0590bbb +a8a4a2865226de9bbf19e12c7e75318439fa6cf1cbf344d5e79a8f363439d3bc5bcf4df91b54581e7866e46db04eaf0d +894582aeff222e145f092ba15c60d3207340c38f2c6792ee2ab4d82d50fb544ae366c2985cc2b6c2f970bcc5f4b46385 +956014ba2d20a056fd86cb8c7ceeab9a2c6f905dae24fc1c5278fa5b84335148ebdefec5dcde8eb9b084700724fc93d7 +af217fe2b654eff6d11a2a79fe0339a1d4cb3708b7be9f09d852158b5a44b4f9b04406d6d67c4f144fb6b69a41ae9d0f +a90752a784bc00df94d960e523f5596695d16a534fc806179e0f878fc0e82a91b25e758e91a165debd815dd1af5f1028 +a697606fb32979549ad822b31df8eaaf50de4ead984439a0a33e955937d326519bb9f62c8243ad37f764655f8d32cc80 +a3ad4a30922e45a3e665551e5611384f1c2d414f6fa806184b0c826af05f014dc872585e255543794ee41e43cdadd856 +b29c255843a82ea74a013bac6c36a694646e61e6b9cefc4c130e2ee261e3bb5da3e0fe3ee7e6fbb009deed0530bc1c82 +87e1cc7febefa829cf050aa2aea59385d1048f8617abba691f7ea9ef58eb90ad12eeb9c439af228b0e34897ba1cf1b47 +994d3222f89e9c8c154362190be7167c8c2662f0cfa9d50eb4d8175b255ff0de09dc548ee312fc8226963c8c16f43e8b +8f1a980be640820f2d1e953264ca4c30330878971669852be3d5d6b41c488be1628b935388bfa2bd4de484acb0fe661d +854d90d0721579c8c88e147a4aa83553c960617b18075f8224b975562dccb30b0e02e81fa9df7070f356a0eeffc3b14f +8e156da9d4330a03e32a25a2f0b861fd3ea5c719fa4f834119baab6e5fa5236a9baaf0d44147bf0841418900037f6eac +96586fc49e53a6799242ddf617000db5a0ad20c6cb1686af2102623d64a71aaddb8e468b15fa6d100d0384e448548db4 +b44d8d85c8df95d504f82d597f8c515866d4d4a326fa1b816dcc5bb0cc4ef1a52647aa5d2e84c62e194c01cae0885d21 +b75c43e676a7efd199f8b32ae31f176ec667e714df355e9eecee97246f72af5bef9c5b04c11e7e90fc37bb9163f957ec +a49835ac0565a79f6a9078cf0443c5be20561a68b448289589721fded55188583f1d301925a34eea647f90a6e66c6774 +b47c17ff6824a00b8f29df0adb7f06223208d062bd703b0f763c6eee4ae62d4217eef2da4f4dde33f0b469c2f2db9e42 +957cf039cea6f6d41e368e2bd0cf77315938a0738f15ed9ca342f0a28658b763659ac1d1a85ecb362f13de12b77bb582 +903a52f8d2439fa63f59e1e9aba864d87b0464ded63814474947112375236a6f84e8fa003cc4433c8208d80e05fbd1b0 +8afd524209ff08d1eb6312b078f7afeb8e1155af649e930ab711dedda226dc2db6b0354aab9652eea7f433f90015bf7b +a95c3c9277b11bc8fe191773bf567641be57c0549913b973fb18740ff9cd7b3f7ce198fa4dc1086b2b8a446012459193 +9455ce8163fce04aeff61e7808ef3aac4725e51404f0858fe5d39d7344f55dcc7871ca332aa5cb1a63a4399529e48907 +809fa35b6958f94e781f2c584438b33f5ed528a6b492d08960cf22ecf63ea3aa1e2d29bc879e17296e0a6cc495439cb6 +b0f50774de212dd33e5837f6b496556215c665437e657f674fc5117e5c07dadbd0d057e6ac4c42d50a8eb81edfebf315 +844c65e263891d0b2fea7db6934cc4b7fb6bee2c1d0b9ab4c47f2eb3e9c5d7197dad828d38c54139123740151420280b +b13c78c9efcbb3b28eb3fe0b971380b7d5151c80948a99cd93c78b4c3ab0e86df6226a64d91e0a2ea4a1c0a46bc0404e +90300a541decad460c348b8f4257f7a29687b2362ebee8d92fd03cc0e85b285ccb0ab1cb2ff5e29c5cc5295e351017cd +ac49b409ded770c6d74f6e70104c2cdc95b7b90609da0743c9923179e8e5201ead03becc0ab10d65b3d91a5be0d52371 +a257b815bd8289dfdfc21af218aaba12ccfd84ebf77642cc4cf744d9b0174ca0b0d7ab2a545c2a314fd5f63c140f41ab +a34778d8446e4d74d8fe33de64b2694ef1e50bc140e252af6eff3ce7b57acf8b6577a02ba94b74a8ae32e5113cf0a29b +ab9e935bcf0d8607e3d66f013d9bce7909962cb7a81174923db02dc89e485c2b1c33d6065bdc7bbbe0450b5c49fbe640 +94d2c5c5c309c9eac04be4636f61bc47fd9579b47aded57cc6c736fefb8dfd8f8a5de32210f7baf2052d04c0219d3b4b +b8dda9046ae265214086355101be3460421f7cd0ed01bde9c1621da510941d42bc93cd8060fd73f374fb1b0a5f38d45e +a6674649dab5f92ab9fa811d9da1d342cf89ff6eff13ad49f4d81de45438e81a384098d3ae5ccce4c67bda5dbe246d95 +8d619f7564677bacba29c346c4ef67c211f7a3a14c73433dd1a7692e16a7e2562f1d0532454af62fc04c2fd2bb1789b0 +a2b93d2fd4c707f5908f624a0fc889e20164d3c61850af9125f47a1719757a6ce6375aa1910eafa4c1e8b6e20c312775 +a07d5585447654d82817ef4d199984542328b238157976eb9a267f0bdb2229acc25aee510be68f65a312b68fdd9e0447 +8ef55cf95e2b24d8ec88e4136399a7763bd1b73d5e90ea45e9845123e9d39a625cc336e9b67988374b8ebcbc75f2ed21 +b62c1fc32e27c767c461411b02fe9aa44a86586e1427406f4ef0b346d077db91952abce79318b382ec75b7be23058cac +b252900345f5fa15a4b77fb6af6a2d04db16e878b7bd98005333f7f6e3c8e6e46cf38fc5d1b2bc399c5c2ff4af730dc6 +a4ab5ac0cc15d3d17b1747c6e3133d586870eae0a0d9c8fa7fd990ebd4fbb62e9090557ca2792a6bc6271856aa3c9a05 +8e706b3f2e902faee10b22742c6c33bea6f670a8937c243db96885143c1db5c979e33ab73a38359b52b8d668ccd092a9 +8a6792190ee6c959d79f60c22980ca140c638d88d75660adaf9bcbe6dc4692ab5f01e0c460170f09f74d5e582e85ff1f +97ffeedfc94c98ec85ea937e064d7b290a326838e62cebd407facd1ab4f08d9c0c109d79af7cb6170fccfa6c8243c127 +b79970b67c09453614ffd83a0c923c17f857c6ce3c87a356298f8351cab0def7ed83efd4f6638f48df67e07bef4ad9d8 +b90f1931c7cf1822cc0a97401119910cdfd0482daf09a4d7612e4e05046295cfb4cc50d5214b31676bb1a1c9d15f9c7f +922921ad813c01fb5d12fa7fb7ed8e0b0abbf7b19affa190b36013c55b88fe3c7df0ae663c970eec7725ba37b95a7cb7 +a124f33e7f28feabb4089a063a08d52b7395d24eecd06857a720439dd9414b7073bb86fbd0b04e7bfac62d3dc0fdb2f2 +b252fe50bc6677c004550f240fe670974a33ffe7191ed7675da6ac36c780c2f8d02be7da5d92cbe2d0ce90147847f8b1 +ae5f8c9c56070f919f3df2d2284348fa4b2e39881f7bc42c9b2f5b7cb1ebeef8ecac000f37329bbe04cc1680cefc7f4e +b432a4575caf7337f11eecfcbd34a6705d0f82c216301725ceae2b3c9df20fa53d1ebef65513e305013d1e0c2df522b6 +b7c016fbbc4614cdbb12db1c9ac41f9a45d5e5ce82594d568a30cd2c66c3cc9d91a2c959697b67c582a0913de661505d +8f6f3e5e0347dddc1b2a34ec0dbbbb7cafbf976f19c9c902efb5c1427d1bbd4b71abd9f3fba20dda75c35a39393c989f +b0042a1d33a1ee9fdf3fad2299b8d70c4f1862d8393b5ebe3ac2189a2c5a58bb826128cd7a39b70d524a6dd976097e26 +85297c4e8ae8d9b44c3fe51aa926c77d55db766c2a9f91b659040de36e34c9a4fc6f44380f8d61704498f6fd52395a49 +8c61a988b6a00fe5a277450f30bf6daa932e42a2eae844568e3babf8815e09311f3c352dae6eb2d57a98d16b7beb2d22 +990be28aaecd932e7edb2a97b9be2789a3905cb88737b1c79881302585801c69a3dd5fb230808b39db1352fc06e0b4a8 +82fd14bdb335aa46f022dfe0ed4d631911e6b6f5eefb10d11e9e2e02a7df55012ed8162249d10b58eb76ced5a7b06cda +ac39cb058df764e161db9c39b185f09aa210bddbd66f681f1697ddbe6b305735612d5dd321d3ffbb4876771bdb321e2f +858a3f7e57ccb81387caf8e89f9b6039e9aadeab06886d8688fe6427151a59ab2e77e85ba850c67d099965426c97779a +b57fb9ea623cec432946819937c6bded0b5d03c8c67b52b44a4b67d34adfb055e6cabca67a48e4d859b4be45162c5083 +b84d2990b563d6d7fe1f4c1894989db25b81745090b94b1fe2ef708ac3b2110ef93d647820b2a51fcf78e3f00fef5412 +817d85b9f5e1521733d2b1fa6d4f4957ac445dc803f97fc495e20b819b14e651332f9e0573d684b854fd47824c53f0e8 +b09e18e97e93a8523101af594422fb71afc5b8826002314269016fcc1b44002d91bcb7c90d923d460f0cc03bddfe9af1 +b867cbede82102de7cf6cd0dae68506869576eaa66c3fc806e73585310602682fc912dc37adf5ff6f0f34a07831735b1 +b1126255798368b692f2796a3470ed16e5ffdee2d8c9e0f7ee3d2e92950c3e6365c32895171c3494aff2a6d6356f7e25 +b05f0a0996dec16335c770a5df3f0b08e20020c838c2caaa1d3a4a2490ede98552f5de349de2ce6e4c4a839731d80919 +98c512bb91c8fa191120ddf5d63c88076581cf41e15eec3c168822f12b3dd0ce4d6df74a7e3093d3e35cad1cb3135421 +84ce38fd97f7f90012c2c1e59a67bf9f465a7ccfb6f308bdd0446cc82b8a26ff7c30e5c7cc375011718cad1b31adaa9f +93139db52c9fb96dee97a0825f21e34c5d6d36838e1e42f4d12d01eacbe94426c85a811fe16ca78e89e08f1c27383d28 +81454037b1e7a1765f67e4288b8742eebf6d864d9b0f508ab44fa3243168ce0ed30cb5f33dfcdb995cd2c2710ff97a6d +828deb2a26efb2ff1842f735e2cc27162360f619b6e3e27a85bedf384912d4726bb2759a3016937973092ece1bf90540 +87e5a7d4e7bd301078f625d9a99b99e6e8e1207c9f8a679f8ebbbfb467bfa0b5f7ef4a4d577c7d2670efa88221153012 +b9dc9d0ea48deee201e34379447bec789c8924aecd030eeb93db159af77eff230976ef60ea9f4b4a9e9e95c1f9f4284e +aa6528268d46bf0627d87d58e243d3ac34b863513c725908a2617e4c6a46ccb1d8c8334bd6dd0eea7ffebec44259dae5 +8d26c9ce07293f6a32a664d31e6df9a7ace47e6c38001635918efd9872aceab62de7757b13b783d422eb67bd28ce7bbb +b0d3ca88d9829a7459b89b0dcbdb8bbb5180b00d750bd959bd110f53c2dd5d4db554b6005c4765fbe7ec5903669e5ebc +a94d1c72bf3b2dc6bfebc9dee40f6a89a516b252bd9f4fad96f156e3dbfc151a9b8a02324d764c7656d59230a18eb61f +88996e79171e30b16505638d8ecb25afd875e5f3cc3e29860937f2b5e751c66e78dc77f744a0cc454a8a655142a93ffb +af4d94f342665fe7ecda318de6cf1bc1c40c37dd83d060fedaf827459728152b5f0e280286ff5e6a0012036f6715f53f +96beaa7a2d565ec14a4e5cb895d33624c69da56b75c8d06ac729cb6d0cb64470ed4f9b0387083cd827b1609c8cabde8c +96b773fa2fcb7377bf71a7e286f37f1f24ee42cba5b4f33903c4566e5e5bcc501ea360e3c8435749107c3de84e272d8e +a69ac6218454c3f40ad0beb48821a218fb0a4f33ebade986d2fffd9a3900d8cfa613bc71676c46cfeaa5f644d1f239a9 +857f139c08fcc45370f448ce3e4915bcb30f23daa4134407fc6d78efac7d718b2cd89e9a743eec7bf2cc0eccf55eb907 +adeeba36af137fd3c371a2adbefea614c3ae3a69f8755ce892d0dd7102fb60717f5245d30119c69c582804e7e56f1626 +afa97ca3548b35aeda6bfed7fbb39af907ed82a09348004d5705b4bb000173270ce44eb5d181819088aa5a2f20a547a2 +8423bd2d07073b0e87819b4e81997e4d3188b0a5592621a30981dc0a5a9d0578fde1638a364f015078a001afb00891c2 +b92e9d4ec3966981ee574695d6e4865810b8e75313e48c1e4bc5eebae77eb28740e97ecc3e5c42040f9eb1ee4b13b0ea +b07b218321d54cecfcd2ed54a5fd588a6be8d7a5b6a66dff7facfe061222c40553e076e57cbdfa0bdb08e0a009c94ba5 +a71e1ae4d6096eac9ea4c21f621c875423de7c620544e520fb6ec3cb41a78554aedd79493cbd2c2ba4f0387f902ddd2a +807cdac291246a02f60c8937532c8969e689b1cfe811f239bfdee0791e7aa0545e9686cfb9ed0c1df84748e5efa5e3da +a1faeb4504c057304d27d54fb3ec681462384a354a4f0b6c759d4fa313253a789250c6b0f44f751b0718592637438a19 +996bcd3215182d49f1cd15a05e1e0a4bf57e264400bf14f7253c6611d2571de7130cce81fd28e0411e0a80e9054f4f98 +89d15b38f14bcd46f4b2dcae82b0e7bf9a35e40bf57aa947e9c4a8f87a440b5cea95229708de08ca596762062c34aaa0 +8d8ddcaf79374c750b8b0b3d196acb6bb921e51b4619876a29d09161ba82a42271066187211ef746f9f40a5ca17b75f7 +a3dc7f70f3a6c7edc483e712770abbaa94bfa3174cfee872b2cc011b267e0ef9baa1ab49e4a6c6c30dbba0e0a1237117 +aa9e958bbdcb192b19c43fc6fd34afcd754949fdada98e9f4848e8db0e23acb27d19dd073c951a8819000f2356aa22e1 +a4714e45ec853eadfe5c3bee7f683b81f97857bbd7833192a48936dd1460aee68f700a21658658b74b737c4fecf90c7f +a1ecab4215c1892e4a8ff3405d710163875e5dfef8a8cb84f5cac4e317d89c7696e3f496ed1747ca6f52b304190f4ba1 +b9b48943eca3686219575026d395b969e6ff8159dc5317005df090e79d26901984e40ae4b1af060ed3ff6f42e0417d76 +9644b9f90a66edb0396abd8c00066886f978ebf56fc22081031fbc9ce371bf9b04aa5a4ef59e59319b3a05bb7fb88b43 +b2bb14f1c055a78596488e4e2d4135a6470c1ee43961952160b8498f674a4d23040606e937c02c1fc23dbd47e9bd4633 +8c61f2fce9a42b94a389c7e52d7d093fc011099d0f4914f6d6f05b631df7b88182826edf9bbb1225971a080ca5c0d15a +aa6a7b8499cc7d256043eacad18528d38bf3be970bea4c6d4cb886690280bdb373688ceba3e506471e1d9493dc76f3f4 +8127703363b3b35b06762c2353d4de82b7b85bb860db1028d3640f46bdb78f2d104fa77ee3e0d9db83833d2b12a966f8 +b7b01f5909f2c66ae0fab156be5d79954e3a304615e1fe55945049dd4bd95f973bb3821117eb54db7e9ed1ee9a527652 +8be47ba5dfe212420649193490838670c40540e0ea24adbab18c4a66e7ac3dcf94f068dec2533b60e08c1f64e7533e54 +905a6c7e24b86aa54a05c329a6b4616d335bb0b1f1e9987562eee0acf82ad302c7c44981a1dd6b24c6121ca12fb92996 +86969ccfd91deed93b355a2c21319e3bb08cc652b741463bf68c626b7ba2afce3f7cc397f2fb74588c2893477c948ae2 +b5a9d20eb12c331d0d300fd4b85b0ac0bb74573178a5fac8ec9dce5e95acba07fab444260355ece442a846737a2dcd1c +a13497c11df21b11fc1a63b0ffdcf7f432da4dc2c98f8d07d36da4fa68aceb57af2158088e5b05e334fe0f264aeb7a97 +882e4597cc66498a45e86a2ed9ee24652da4699af00ad35f73b5e74fde6ac3cee70630962d5ddd86162d4aaf11bbc11c +b748858c2bafa4a14ce44af35195e9c52aa75e109719243bbe278095acbfd6a7ae7e084caf8dae6939039b5a4e8fd675 +83a2e0524507e74f51fe976441108f8226ba1b3a33f4e16ec45c5661ce80cb1840a93d17122cb8ca9e0f80d14f69877d +846cd2946c93ee5f24243d9ebc69936b3a1a6d59f45fec6c79b1eddf15ce30a8e73ad03cf606ee66baea3d8ff115f70f +8d98d0a3a94f6efe158f8423c041b546416145c5c2254bfa157efea0d1c99fe58acc7df6424ef29f75960b18d664ea4e +a39fa47e4b79f54dbf59d0b1726f1e78bc219fcfc56ad238c84b4b610e7892ff1e65d537baf5118a32f5e2eb80d5ee0c +8c30969a4519131de5e30121c84c04f67b98c8ad109fa4710dd3149cae303d51778add3f258f0482f1c89c169824dffc +af7f80d141ceb78b4762015de17fef49d7ff6202d292e9604deb508272ee7569f7fd5be3b2438da1dfecf0c26533ef86 +97cf82f70128251944d79b8845506975405bd720e150d836205b048ff36ba8801eb74cdcc6425f28f6bc0acec0a81463 +8c276c876eb88688957d1868bf3a1462375e608ff72b49870a5dac82cbf6584e00e3f36f236f732348a47502ccf9539d +964765f1a5c8a41d8025ddf56dc01b78424703d8a64a4e5539e477cb2445cb541c70127c561e717256d13f91a830ba83 +a2aacd9e21b8c8efaf2319611addea1b9f41430aee42e7f2a640cc693aa395287cc8fdc2806b76b577d84fbd05378ead +ab11eabbf5be4345a77323a3b75f9ee93b011fd2a9d0154e88183cafe47f82a7888666af16b40d3cb677c94bcc755ff7 +a0bfe715a7af5a29b1b6148b8cbee585d2b49fa6ce59bcd173ea3bbc60d71a62f9da27ffcbbd5a6da75502112fe44d70 +902e6cc38ee42245103d90b65028a471bc7a48b825599d361aa81d8c56e0fcf9fbe8d4c13802040d2cfb85b7e022eea1 +8832e2b5014fdef4003bdbb87e3298fdbdbbe49673f6b66e2373f1cb2605f9c4af2cdf9bfd45d1993208681d29ee1c9d +a7d39d3fa1ec1e0c87730fa43d4900e91932d1cafb36c76b2934907becf7d15a1d84d7234591ad4c322b5a24673bba8d +836ed5f09d99624204aa3aa7ac601980fda223f3b4b96b4a8fb235c574a3545d518787c12f81bd5851987f2860d41886 +94235e94445e6086f6e9331923262070a4c2ed930ec519eabb8a30133bd4fc6debb99185f4b668431fae1b485c5c81b7 +9828ffe20b9405f117dac044159be2d3c6e2b50ecdd1651d6a73f7633e6e2a7ba3d783ae939973604446d3a1ef0fb20f +92f03dc365dfe9154743ca70e6dd2758f064e3286fc543cf8c50f68effdf7c554bd17b3507c6ff4127046d9bbb5522ef +91ed07df479d8eb3d31292a0e987672a7f3d45ecafe72935b7abbc3f23493605134ce573f309e226c9efe830b6868220 +93bee582661e6d6cefeff29002afc2f36dd2c13dbf33f0574c35b290ddc426170a5f7f196369ad592efcd72cfb6f8fc0 +89a51467d966f48fed15dea5a12dda54d0015f69e2169b5e34f44c7b5a5d4c282d6f138116a0cd06a8476980e420f8d8 +b8ccebc14b6679ba2399370848864f15f63512fd6139df7359b7b93e82c1007fd85137ecb0597294b46643e1a9e7ab5e +841fa301567fc57b2cd09508ce75326684e12bfb8add671dc208f579b2500b93d5b641e9f59bba798ed4ed1259757f7d +b3cb45c15eb00b4ccb7013299f761cb8fefc17adf6db50e9ecb8abe927a3bc7f28e359e64693813e078e1dac800ad55b +96e55d3b9f445f5679e34fa5425b3e87cb221cfbdd07f8353868c7f7f4ba388ee3841cb9a1d638583bc20d03a9d071f2 +a7dee9377de740270c5b57cf86699004ba8dc2766af56b388b5cb0814ec71bb99ecf43ee3d82a552733854ecc7def0fe +b129dfff23b3c1c95ddb214c4711961fcb129efe2b6557ec9e116ada909593d0d2eec2c628434493393c58c52aa86847 +aed2670e201cb3e38a8be3c86735a4d76255e1e5a4c67b91df6ed262d09c8d10b0a3891da3e6ab934058cc9a7178931b +b20b8921ae52e5b3c94fa3a8b46489044174f7b897779e7763d6eb419e808d76705b7e7ba5131576f425aa81b6b0de53 +a7e45bbc3ba1bc36617291ba7663806e247f1b57a89e31520c64a90cbf8d426cac2e2f381338baf78c8f92fdbbcb7026 +a99e651e73a507e9e663e2364fcc193ec77e8afdc08c2bed6ad864e49b537ec31e9114ee72291a7657899f2033a849e2 +af966033636c2e9e8280d173f556fe07f8b6940bbcf6b2df7e2165c30bea66cced2596f6c17ca7c1aa0e614174953ba9 +b69ca7a79e3d55ef21e0ebdc6f0c4bd17182d30cf6290cccca7d2551c91c12b966020d8e40e4ee4179488c9809c03ae4 +b981cd36244e035fef043f70b1d7188d7cd045b4de0581c459fc5730e10eb7f3d5893b54cc4243849c0855e4e621167a +b20fea858a36921b35a3051ce787b73f70fdecd3fef283c15a2eb1bffb1dcba5991eee4a047ce4e87802da923fd9457b +b040e6f2e56dc1860274c263d4045837456f74b354a679f6b5ea70919835ebe5d32bf1f519e218730096c98ff396dc9d +8d2dd60e702c923a7204b530e7d6c193c6f93ca648c4f7bb38f4edbeb0aaed84184213afafb8db6aeb9197c24364276c +95dfa7348709e43d71285b28a0bfad3ca805b6ed4ae99753e9f736c79d58a35a3a50b42760ccdd03eda50f6e59494968 +b8585632a13f18c139a411bb2f02df809591834d127cd1ff081e26d0abfe0e3fbb54abea26538b25a0dcb4d7e969590e +b46ba47858a29c6d523c9982660949567666daf2582b93393a4802a9e077eedbc0d49d454731696bc8e46ca50c7caa40 +84b756b901b98a4404e58d70f39f6ccac877146c866732ae65e7e82727448d1550343bf7cdff1bfd4ee1ed73793db255 +83e5be888eaf877a2c755897410865f64a6d1169a8ccf0336092f3932abab915e542ab75a35ffe016042340d581ee987 +8cb274fc39285aed451a7def72cfbf73168ee10be02affe355a2bf87cf361a81ad284e9334cf00c5bf99a13d9f75e116 +91ff6220924b94ae13f50eeac16a159232e4f16a73fbd5c22c0e185cd1998403904d36bad203baa82b85819ee4a8ac10 +87f46e08e09aea2ab37b55fc300689d9b58ff3e72f1cffe023386035888f714fac4673c7c5193d3f3f3c568c640694f0 +835d7d84ca7641e1b15095830114aa6072fe12260d2202456cafe2308c22651af9ffbcf6b7e56af97167dd0c4e2a4cf2 +91202183f79794f114fd9e3b9bd05553c0e8985919965101a57d97ef666b028863e6cea9735af016dc1864f1542dee51 +81ab2b02a9b0a490a74ae615ddd4fe560734c1bfdde6b8dd13303c1481ba0e8ab14473535a93cfe4e824a0ab29445f8c +8a32d73f4fc006551d4e2c61eec6130355ec9b8c39a65c24ec1edc00e80155ca83a8ef2455e892521a3d47634d82a987 +af70d7b8f13bc90193cc1cfb0c400c4224cf10f1887848aa93e6380f7087782fc41a159926ab53c53eb95c2383b1a849 +989bf42f9d357c51774f1c7c0f7c0c46a8cb7398a74497141c32685be098e38b4230ffe833a6d880ec391a35b1a747b6 +94cb6715ee95700020c630b8c19e35f231de970219bd7e6ba7ced01899197da473b6c45cacfab0d652ddaf547b4ea58c +b12e3331f1f7d7458393a785e22e9a5e1d1daea521b4e78c0ee8ca59b41ade1735a29820e18f6afb2f2c3c56fecc16b6 +ad4b7cf654349d136fb41fb0dd65b588199f68b462b05f5c4e5c2b468bfaa6c26329033e3c3f7873dc8ace89cf873ea5 +a3279969e1ab596df0559ffc5ac7a6dc849680354e01c3f4fd34c6413a3f9f046f89c1e1be0b315d8b6dfab3d23d5c14 +ac74cc5562836ed89d09a9ae6a3644c936d64bdda9e77659d9982f1be29541b03ef2723236d5465e398373ea19a4ccc6 +98138ebce1af531dd8b631b3e74c84f0c700355a2a9bde31e5e51bb10c8bbd766559c63f6041f4002568803fe08438e0 +9006445da131349fe5714e0777a4f82a82da343612589a0c1596393e8b6894ce1cf42784f95ff67a8384ffe1f1a4ad76 +88502a84a85e4ce54cfed297b5d355867cc770a8ffd0714a6f23b1ab320a9903c6e42809e034bb67dbf94c4fc0d9c790 +aa8b4bf123d1a6ccaa44b86be8f980005f2a0a388a76cb111b0e85cd072ef64167fb0c097c7b23c4bca64c0260f6cce0 +ad49eb35dfea9feabb513a78dd1152ad7eba22fbb02a80cefc494a7037699c8df81202dfec12acc1b9e33ad680cb72d2 +8694da730231b29afd5196371ddcb15b4dcc499574bdd063f4864ab80749833ea38ab8b0ca1629a367fe378e87a60a86 +8eca7b488e810c479e7e32e24b8afcd837f7df183fe4f621a0336b53a9ed77603c84bdc365d8be68179a32b71a1deb7e +8875cd3e23c7e1af55af1b091025a08255743984186770bcd43f30b4a58d175cfdf1984bad97a15e08dac2da27198c3d +abdafcf58ec72997e494d4714645f40d09dcd0fbd0733e640eca44eeea67c25bb0c270299c459991f2fae59d13b4f4d5 +8f040970141e61489284f3efd907705eae6ec757fe8e1d284eac123d313e9ac1e8dc14ae3f04d281e1effc49d5d2f51d +a7ff115f0d2dbf66c0e8770b3d05157b37357b9e33e9a447f0f3fa9da69ad04e371fd1e4848cfb9e8d05e3165bd969d8 +a39b1a8c39d317fcc97bf6c396e6ed4a85640aeeadbf45166bd02bc3bdfb6266509159c03afd492e642384c635b824c0 +a2e1b90f3dd2d0038eaa5be52127844ccf35d997143179d95ffd3749c0896398b130094d01eb1bb31ffe80ef34b42b48 +a2bbe31f89b0c3c375ffaf63c8b7831860a921d5e388eb7907dbf61f2601ea40db86bb3952ecaa26a5eca4317a848ff9 +87d885bb0f2ce04b40ce94d2557c15f1698dc652e938f9a2d69a73ccf4899e08eafa1a59a20cae92823795f5b94f04b9 +8f7746370f8a24a2889d351f3e36b8a7d60e75e50e8f5abeea7dafc75441e95915721654e61ceac51bb6f112780d352c +a7272847526ed3d9e0d0fea1d8685b07b5b908971490bf8a46748c8b1783c629b8644feb5bac772ae615daae383d5e72 +978c9aa2996d8bd6fda7e0393fa8b38747f8f99712427705c00f6e9a12c36f8d8b4cedb03fcb9867155cbddb5200e6e1 +a4dec4a2354b2b32434c5bcdc380bf84580c6f9940f94dc0498a5bfe89c675a0921e66b807a3d859a6059a464cb2a9ac +99459ddecc7abce437f68722dae556d8ffaf8ed974f459e52e6d4a64f176caa4d42c2f2ec57e8a5b5f2034638e8acb0a +928c68c0c9213fe6258ab5bb0c693d97203d15da359784de7824dec143212da57d062a1fc70a79172cee31adc7aff382 +aad3f318f1622ea87e12541dfd982d71629b8f1ded4c301f9f6b6af9432716ad057773c33bdaa6f15dc151b0ee4505ea +8eb8e978f149a983fd6ad01773f9aacf57bd0cc622d8a301e404184b37e610123dd081faeda571a0ab1f149a3960af10 +851e7191d7b94bd422bcece5b92609fc1b1c8556229bc53e32963b2d2fd1cacd8ce5da9040b599eca6e610540f8a7987 +9414157fe9d50e5a0b5a7397417681bcb3a651eec1cab63f2a88d5df68ab1fef6e4c1d7ba657cbaf241a7cb790297633 +b5cb2dafdc5408959780754a58b2da55b2a9136672ebca42f34da4e329ddc89360e7218cde3efdbf784ddb390deacc57 +ac6b70f65503a8e94b773fda3e72615745824930114fe72b6d833484285462392617c1b2eea4a250fedbee88f503f3ba +b0829a5312f9ac6c06fddee2f835a3452fe994f6d42c9edfc390d7d5b3240ca544433b544cbbddd6516b38a6d5d7c21d +95f8e2c59905957e34d53be3d6fb85732f834e2cb9ab4c333fea2f502452a87ccd035fc9075d7c0bd8530bb0a0c96527 +b93f279b7045f2d97c674495f6e69a3e352f32f43cc60300193b936c2850b2805c15457251f7e3f633f435cb2b60405c +915abf16cba1a0b655b92a8a70c03e7fb306b86f3bbfb66967ca63e64c003b59c7a5953675efa4fa0bce9bed536b6700 +ac2047f50a319d09df1ec44d71afdcec5ac3bd2765dc98aba347734aa780863545df9f6d71214d443e3f37edc0dae45a +ad49c74ddb24c8a26b14ec08bc807313c77c5967fbb36237f55994d7511bbac8d7e7b9b8ec53eb1b3b066989f078dbd9 +961483105f605e959213fe9e8a52b76dac62d7efd2319ec71fc4e92d68fbe44cd2f65d7adefb2eb64d591b91648b8085 +b67fcafc97d8df2b3075bbff7b3d7471dbf1f3048f309e55d5e2c5bcbc7a73aebcb0697859be9f387cbc7ce98041e154 +8da70ac16468cab6066992389cb37c79ff5e0babbe67d76878aef9408b9597a3dc2eb5de87428bc761a0d78957b0eb28 +aec0ce89770d299b631f15ae12f94b1e1014ac57d38fcf037c2c7712d770d074affa06e97c60691bad8733874b6ad2ed +8b702c85fa4c915a09fc86507f44d7aeda0993b77af87780d70cc98d580c6e996b64b7c16cdb4dd4562cb0f75da36ee7 +aaeb43aa472aac2253e211fd1066c3a5422ea041cef20168702d0618a1a742a44f7fb30a76677640fea1a24e7fae1996 +a8820e92825d6e02b9b4ad5ebc86161d3244cddd3d244333ba1576b6ae10948145b68d9e926bf6b7a2c25dab4cf43f3e +8ffdae28a1f1d15d7ffa473628a66ee9a739073f59ba781248286b39cb8f7255f66d62337064246713cbb5017e615174 +adfc5dd142b7911326d8424881d5d92006f3b17de4cce91674d6ea37f00fbb266c791ac13f6c7a0f61d04f2a952e6a04 +87f98982444bf661f539bec73a10256f079a4baa88a1cea0351ae3de929e1c500485b2d1b5d933063cd7d9123d5050e4 +8f217ba4dd404c5ee384f0c9a126686db001ff0344c01c82174c5e5ef89d1a241b146008c534b13a0da6c8afe7450fbb +afc85476dddaf1cbb4ba8b22186789f3818c7964f9f613e55010278800cd95422702248bdf9c73760702ef24854795ec +a59e0f6ac2ccdfbd01f002008034390c0ea78716f5e0de4e474e3558755705c9c7afb6e3c5c4370e7bbc85958a9c7a63 +97c0695c58d792ec31d9b86d3b2fc1382f0855057b24d5f6a54c41f76f9e2f52882cadc89a8b2f121530e7f1393faa95 +8e49112de0b2649c08a96cf737af68fa8055f1af594846a2d0534c94df6f926f200405edaa6e6ac9db7e380707a2571d +99a1bd83a7ac5f8d77ddf044c80ebfc5745b998714696d67b94d185c97e9d6db989bacac646d9def463127a8b2febc00 +aba80725f9f9f7abe10760eca73ba427ca8df864a157122eb9af828a05b0199de3add02019a297750bdab5380e505c58 +ae18f62573275c1eb268f74c5e54e8958547f9e7d1d36a05b084eb53e5704fafe2200b8aff95cc7e9af5be2391c42b7c +908b8031d09d22b2aefeaa876a998e0a97c7a1070aad9e9c97836cc5aa6d2d5ef94230e1222074837b5e21b4e6490f01 +b3132282e8b41ca6789ec5c43c1fecf3a65b8eefbc2f3d10f746a843b9ba4ce6db664678e75e424f7b11a00c1440de15 +a1eb49440cc106ebc09cf198c93e8070271eb5a936d31c04858a2b311a037350100c7957d5545c9653f396aa968b91f4 +81df6ad1bdd5eee4cc2f94318467b8602d15cc1be2b48b09ade12cc46ee05cbaaf77a20397e5015030b1f1db5dd9dac0 +87236c68a2a93c8442d15d7f1d1dc01d1fd123439c183e1d843f4ddd2bcf638c128f66f1ef9b710e5d1f64a52726007a +84f2e7f85563bb2f61b10a712c7605d63f79af5be0dba056814fd3efebc20e9c53227c56577b72c68d185571b775eff6 +a36d4ae06688ece2927aeb2c7f058a3cd2aa1de1601282d4e688e1d76ef20728b892928deda2314eba41675eba3912f1 +b8326dcbcdcfce017b263c456c47692fb476c4225c95981666fff0b7d4522fc23b7f12273f0f47cf0442662124e6648f +84c66463ab277cda2cc7007d0509269e89cdd41c5e0d3773a92615f0fc5da63811186b05d7a11088048a5d4834a7e0df +b20d3571d970712ef4699b0e7034fd269c361f53e1572e2ea2676b4245e992d43b8b5931a801439a44d977a988cc360b +94dba6007e6d4998ca1eb84aa8e2a7e9f5c164b9d80df2825f2208ce5640a05aacac2e4f08918268990f43ae1ccab69a +a1c25f0b3ef9d1982153207570d9ce8d692e1b6963b509958dc4d9bcd80074bb221c46804a6d9a29e76149cc7787c282 +8857748fcdab1199fc96084323a81d3bd8b5a7f0b1abc5bc3b5252a19268344e2e7d2d086c90fc9b5fa4b92feedb93a4 +8b9c1d841447354b6c086549e4d1d435ab64c13933488c34bc30f0f6eb36c5c5b838b7b6bb018542247edd1ada091045 +8f5b655416da0e719a204fc567e93792c301acb4374cf7bbabc6ce51dbeaaadfd75c2db0e16ce073ab8e91fd3d7ea9d4 +90f2846b19be46a75c5cd0cafefcf9192e6fd80c479e8d6320c4b8d8d7d96703c9e77ff31a67afa9858e6b7bde1f7cce +a53e383947fd98aa1a55ac956214b46b20a52758461e8ba41341a23a835ebb713038bf048edb1202bbfd0b56a96bf292 +9542d7debbcfb9cda6fa279c699a7b655c03b9a9b456a5d3cfc41a826c94eafa43e01155a29e39ff0bcd965f4c0c512d +a43792864ec5fc549f7afc02622454afc0e425c310c4039ba615067243ebb26a4c7ebfd19bd4d57ff412a4bb2a7958a0 +b85123950e30c048465bf32365d24a5d4b21fffc6183cdbf71643a07b87463989b72dd9a6a47f134856f704909a6b38f +944ea689aec1376f855c0bc9c51378ad06ff758a2c075b95a60b535b88b36eca0be11e4edb5152e98cb2137d6e749f27 +a6bef52cda22325e4c62d323e2a0e3fa91c5552fcfce951edfd52ad6f652bfdcc2341f1cd349e6b5d447924dc569bfe2 +b56bff8ffe981bfcb30791836da10b87f2ccbe17ed969e7f7a650af07d27ae0223805b1264d985148208483be50578a6 +8b209cac898dd580c82d854a553e2517497ad1a4cd198e1360b8b50639b380aee70ee4b87625d9b2278228ff644cd25c +877cce233fec74c7158b3c5bf108365e98238418b8a71f058f1aca44a0fd3a1021e3e9025bd11fe244d9fe0f5034ce7f +b1b871aeedb03d6f6accc99816b89f5958178738d8d8cd9717527d04363c80fdb5f6848122ae19fdbc450cfa11e753c8 +858aca51b9e5b0a724e88688d5124eb24c9faf01a3d465e74d31de6da315f311143f22f60201ea09f62c92f61f09d889 +8521d409615dfc8c8289e00f6aaa6297c2c4e1439b25952afd76aac641b81c70b9cef07cd58c1c0198382bddd2bd8544 +88647c3e41666b88acca42505f1f5da226937e0522b538fe0cebb724e9a99730ca2522989e94a96cac94109aef675c0f +b417fdaf719caf38854e89ce52031b30ce61a632e6c3135adec9002280e022d82ab0ea4ac5ebdb21f1f0169e4c37bcda +9367a6feb5e23ea2eab8ddd5e7bdf32b4d2419fad1c71a1ed327b77362d8942dad971a1c2e6f7073885149cdf0a0c339 +a71c5c08d50c57d094d6a4f02e97d3799bada92f238ffc07bd223bbe8379507b7310d20b28f5bbbf331e5e153515e491 +9630a9a3bcb044b51299c4d3d3388a4ff47308dd27be3229601985478c0f6b55faa7e20815d8694f910611396a9d0d45 +b0bfaf56a5aa59b48960aa7c1617e832e65c823523fb2a5cd44ba606800501cf873e8db1d0dda64065285743dc40786e diff --git a/crates/starknet_os/src/hints/hint_implementation/kzg/utils.rs b/crates/starknet_os/src/hints/hint_implementation/kzg/utils.rs index af365d365ea..8ef14b0715b 100644 --- a/crates/starknet_os/src/hints/hint_implementation/kzg/utils.rs +++ b/crates/starknet_os/src/hints/hint_implementation/kzg/utils.rs @@ -7,6 +7,9 @@ use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; use c_kzg::{Blob, KzgCommitment, KzgSettings, BYTES_PER_FIELD_ELEMENT}; use num_bigint::{BigInt, BigUint, ParseBigIntError}; use num_traits::{Num, Signed, Zero}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, Bytes}; +use sha2::{Digest, Sha256}; use starknet_types_core::felt::Felt; use crate::hints::error::OsHintError; @@ -43,12 +46,12 @@ pub enum FftError { } static KZG_SETTINGS: LazyLock = LazyLock::new(|| { - KzgSettings::parse_kzg_trusted_setup(TRUSTED_SETUP) + KzgSettings::parse_kzg_trusted_setup(TRUSTED_SETUP, 0) .unwrap_or_else(|error| panic!("Failed to load trusted setup: {error}.")) }); fn blob_to_kzg_commitment(blob: &Blob) -> Result { - Ok(KzgCommitment::blob_to_kzg_commitment(blob, &KZG_SETTINGS)?) + Ok(KZG_SETTINGS.blob_to_kzg_commitment(blob)?) } fn pad_bytes(input_bytes: Vec, length: usize) -> Vec { @@ -72,6 +75,10 @@ pub(crate) fn serialize_blob(blob: &[Fr]) -> Result, FftError> { .collect()) } +pub(crate) fn deserialize_blob(raw_blob: &[u8]) -> Vec { + raw_blob.chunks(BYTES_PER_FIELD_ELEMENT).map(BigUint::from_bytes_be).map(Fr::from).collect() +} + pub(crate) fn split_commitment(commitment: &KzgCommitment) -> Result<(Felt, Felt), FftError> { let commitment_bytes: [u8; COMMITMENT_BYTES_LENGTH] = *commitment.to_bytes().as_ref(); @@ -157,3 +164,129 @@ pub fn split_bigint3(num: BigInt) -> Result<[Felt; 3], OsHintError> { Ok([d0, d1, Felt::from(d2)]) } + +/// Structure to hold blob artifacts: commitments, proofs, and versioned hashes. +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct LegacyBlobArtifacts { + #[serde_as(as = "Vec")] + pub commitments: Vec<[u8; 48]>, + #[serde_as(as = "Vec")] + pub proofs: Vec<[u8; 48]>, + #[serde_as(as = "Vec")] + pub versioned_hashes: Vec<[u8; 32]>, +} + +/// Computes a versioned hash from a KZG commitment. +fn kzg_to_versioned_hash(commitment: &KzgCommitment) -> [u8; 32] { + const BLOB_COMMITMENT_VERSION_KZG: u8 = 0x01; + + // Get commitment bytes (48 bytes). + let commitment_bytes = commitment.to_bytes(); + + // Compute SHA256 of the commitment. + let mut hasher = Sha256::new(); + hasher.update(commitment_bytes.as_ref()); + let mut hash = hasher.finalize(); + hash[0] = BLOB_COMMITMENT_VERSION_KZG; + + hash.into() +} + +/// Computes KZG commitments, legacy proofs, and versioned hashes for a list of raw blobs. +/// +/// For each blob, computes the KZG commitment and the corresponding KZG proof that is used +/// to verify the commitment. Returns `LegacyBlobArtifacts` structure. +pub fn compute_legacy_blob_commitments( + raw_blobs: Vec>, +) -> Result { + let mut commitments = Vec::new(); + let mut proofs = Vec::new(); + let mut versioned_hashes = Vec::new(); + + for raw_blob in raw_blobs.iter() { + // Convert raw blob bytes to Blob. + let blob = Blob::from_bytes(raw_blob)?; + + // Compute KZG commitment. + let commitment = blob_to_kzg_commitment(&blob)?; + + // Compute KZG proof. + let proof = KZG_SETTINGS.compute_blob_kzg_proof(&blob, &commitment.to_bytes())?; + + // Compute versioned hash. + let versioned_hash = kzg_to_versioned_hash(&commitment); + + commitments.push(*commitment); + proofs.push(*proof); + versioned_hashes.push(versioned_hash); + } + + Ok(LegacyBlobArtifacts { commitments, proofs, versioned_hashes }) +} + +/// Structure to hold blob artifacts: commitments, cell proofs (CELLS_PER_EXT_BLOB per blob), and +/// versioned hashes. +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BlobArtifacts { + #[serde_as(as = "Vec")] + pub commitments: Vec<[u8; 48]>, + #[serde_as(as = "Vec")] + pub cell_proofs: Vec<[u8; 48]>, + #[serde_as(as = "Vec")] + pub versioned_hashes: Vec<[u8; 32]>, +} + +/// Computes KZG commitments, cell proofs, and versioned hashes for a list of raw blobs. +/// +/// For each blob, computes the KZG commitment and the corresponding KZG cell proofs that is used +/// to verify the commitment. Returns the internal `CellBlobs` structure with native KZG types. +pub fn compute_blob_commitments(raw_blobs: Vec>) -> Result { + let mut commitments = Vec::new(); + let mut cell_proofs = Vec::new(); + let mut versioned_hashes = Vec::new(); + + for raw_blob in raw_blobs.iter() { + // Convert raw blob bytes to Blob. + let blob = Blob::from_bytes(raw_blob)?; + + // Compute KZG commitment. + let commitment = blob_to_kzg_commitment(&blob)?; + + // Compute KZG cell proofs. + let (_, blob_cell_proofs) = KZG_SETTINGS.compute_cells_and_kzg_proofs(&blob)?; + + // Compute versioned hash. + let versioned_hash = kzg_to_versioned_hash(&commitment); + + commitments.push(*commitment); + cell_proofs.extend(blob_cell_proofs.into_iter().map(|proof| *proof)); + versioned_hashes.push(versioned_hash); + } + + Ok(BlobArtifacts { commitments, cell_proofs, versioned_hashes }) +} + +pub fn decode_blobs(raw_blobs: Vec>) -> Result, FftError> { + let mut result = Vec::new(); + + for raw_blob in raw_blobs.iter() { + let mut coeffs = deserialize_blob(raw_blob); + + if coeffs.len() != FIELD_ELEMENTS_PER_BLOB { + return Err(FftError::InvalidBlobSize(coeffs.len())); + } + + bit_reversal(&mut coeffs)?; + let domain = Radix2EvaluationDomain::::new(FIELD_ELEMENTS_PER_BLOB) + .ok_or(FftError::EvalDomainCreation)?; + domain.ifft_in_place(&mut coeffs); + + for fr_elem in coeffs { + let bytes = fr_elem.into_bigint().to_bytes_be(); + result.push(Felt::from_bytes_be_slice(&bytes)); + } + } + Ok(result) +} diff --git a/crates/starknet_os/src/hints/hint_implementation/output.rs b/crates/starknet_os/src/hints/hint_implementation/output.rs index 19547edaa84..97fc48759a8 100644 --- a/crates/starknet_os/src/hints/hint_implementation/output.rs +++ b/crates/starknet_os/src/hints/hint_implementation/output.rs @@ -196,9 +196,9 @@ pub(crate) fn calculate_keys_using_sha256_hash( // randomness source. let compressed_start = get_ptr_from_var_name(Ids::CompressedStart.into(), vm, ids_data, ap_tracking)?; - let compressed_dst = - get_ptr_from_var_name(Ids::CompressedDst.into(), vm, ids_data, ap_tracking)?; - let array_size = (compressed_dst - compressed_start)?; + let compressed_end = + get_ptr_from_var_name(Ids::CompressedEnd.into(), vm, ids_data, ap_tracking)?; + let array_size = (compressed_end - compressed_start)?; for i in 0..array_size { let felt = vm.get_integer((compressed_start + i)?)?; hasher.update(felt.to_bytes_be()); diff --git a/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/state_diff_encryption_test.rs b/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/state_diff_encryption_test.rs index bb8dbe1e48c..f03ba2dfb3a 100644 --- a/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/state_diff_encryption_test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/state_diff_encryption_test.rs @@ -137,9 +137,9 @@ fn test_state_diff_encryption_function( HashMap::new(), ) .unwrap(); - let encrypted_dst = runner.vm.add_memory_segment(); + let output_pointer = runner.vm.add_memory_segment(); implicit_args - .push(ImplicitArg::NonBuiltin(EndpointArg::Value(ValueArg::Single(encrypted_dst.into())))); + .push(ImplicitArg::NonBuiltin(EndpointArg::Value(ValueArg::Single(output_pointer.into())))); // Use the parameterized data instead of hardcoded values. let (data_start, data_end) = add_memory_segment_and_load_explicit_arg(&mut runner, &data); @@ -173,13 +173,13 @@ fn test_state_diff_encryption_function( ); }; - let encrypted_dst_length = (encrypted_dst_end - encrypted_dst).unwrap(); + let encrypted_dst_length = (encrypted_dst_end - output_pointer).unwrap(); assert_eq!(data.len(), encrypted_dst_length); // Only try to get encrypted data if there is data to encrypt. let encrypted_data = if encrypted_dst_length > 0 { let encrypted_range = - runner.vm.get_integer_range(encrypted_dst, encrypted_dst_length).unwrap(); + runner.vm.get_integer_range(output_pointer, encrypted_dst_length).unwrap(); encrypted_range.into_iter().map(|felt| *felt).collect() } else { vec![] @@ -242,9 +242,9 @@ fn test_compute_public_keys_function(#[case] seed: u64, #[case] num_committee_me ) .unwrap(); - let encrypted_dst = runner.vm.add_memory_segment(); + let output_pointer = runner.vm.add_memory_segment(); implicit_args - .push(ImplicitArg::NonBuiltin(EndpointArg::Value(ValueArg::Single(encrypted_dst.into())))); + .push(ImplicitArg::NonBuiltin(EndpointArg::Value(ValueArg::Single(output_pointer.into())))); let (sn_private_keys, _) = add_memory_segment_and_load_explicit_arg(&mut runner, &sn_private_keys_vector); @@ -275,13 +275,13 @@ fn test_compute_public_keys_function(#[case] seed: u64, #[case] num_committee_me panic!("Unexpected implicit return value"); }; - let actual_public_keys_length = (encrypted_dst_end - encrypted_dst).unwrap(); + let actual_public_keys_length = (encrypted_dst_end - output_pointer).unwrap(); assert_eq!(sn_private_keys_vector.len(), actual_public_keys_length); // Only try to get public keys if there are private keys. let sn_public_keys_from_memory = if actual_public_keys_length > 0 { let public_keys_range = - runner.vm.get_integer_range(encrypted_dst, actual_public_keys_length).unwrap(); + runner.vm.get_integer_range(output_pointer, actual_public_keys_length).unwrap(); public_keys_range.into_iter().map(|felt| *felt).collect() } else { vec![] @@ -336,9 +336,9 @@ fn test_symmetric_key_encryption_function(#[case] seed: u64, #[case] num_committ ) .unwrap(); - let encrypted_dst = runner.vm.add_memory_segment(); + let output_pointer = runner.vm.add_memory_segment(); implicit_args - .push(ImplicitArg::NonBuiltin(EndpointArg::Value(ValueArg::Single(encrypted_dst.into())))); + .push(ImplicitArg::NonBuiltin(EndpointArg::Value(ValueArg::Single(output_pointer.into())))); let (sn_private_keys, _) = add_memory_segment_and_load_explicit_arg(&mut runner, &sn_private_keys_vector); @@ -375,13 +375,13 @@ fn test_symmetric_key_encryption_function(#[case] seed: u64, #[case] num_committ panic!("Unexpected implicit return value"); }; - let actual_symmetric_keys_length = (encrypted_dst_end - encrypted_dst).unwrap(); + let actual_symmetric_keys_length = (encrypted_dst_end - output_pointer).unwrap(); assert_eq!(public_keys_vector.len(), actual_symmetric_keys_length); // Only try to get encrypted symmetric keys if there are keys to encrypt. let encrypted_symmetric_keys = if actual_symmetric_keys_length > 0 { let encrypted_range = - runner.vm.get_integer_range(encrypted_dst, actual_symmetric_keys_length).unwrap(); + runner.vm.get_integer_range(output_pointer, actual_symmetric_keys_length).unwrap(); encrypted_range.into_iter().map(|felt| *felt).collect() } else { vec![] diff --git a/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils.rs b/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils.rs index 623471ebbf5..1854bc3966f 100644 --- a/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils.rs +++ b/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils.rs @@ -3,6 +3,10 @@ use digest::Digest; use starknet_types_core::curve::AffinePoint; use starknet_types_core::felt::Felt; +use crate::hints::hint_implementation::kzg::utils::{decode_blobs, FftError}; +use crate::io::os_output::OsOutputError; +use crate::io::os_output_types::{PartialOsStateDiff, TryFromOutputIter}; + #[cfg(test)] #[path = "utils_test.rs"] mod utils_test; @@ -146,3 +150,36 @@ pub fn naive_encode_felts_to_u32s(felts: Vec) -> Vec { } unpacked_u32s } + +#[derive(Debug, thiserror::Error)] +pub enum DecryptionError { + #[error(transparent)] + Fft(#[from] FftError), + #[error(transparent)] + Parsing(#[from] OsOutputError), +} + +// TODO(Einat): Test this function in the OS tests. +#[allow(dead_code)] +pub fn decrypt_state_diff_from_blobs( + blobs: Vec>, + private_key: Felt, + committee_index: usize, +) -> Result { + let decoded_blobs = decode_blobs(blobs)?; + + let n_keys: usize = decoded_blobs[0].try_into().expect("n_keys should fit in usize"); + let sn_public_key = decoded_blobs[committee_index + 1]; + let encrypted_symmetric_key = decoded_blobs[n_keys + committee_index + 1]; + + // Decrypt the state diff (may include trailing zeros from blob padding). + let decrypted_da = decrypt_state_diff( + private_key, + sn_public_key, + encrypted_symmetric_key, + &decoded_blobs[2 * n_keys + 1..], + ); + + // The parser will consume only what it needs and ignore trailing padding. + Ok(PartialOsStateDiff::try_from_output_iter(&mut decrypted_da.into_iter())?) +} diff --git a/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils_test.rs b/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils_test.rs index 315b97fd027..18e464f9a2f 100644 --- a/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils_test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/state_diff_encryption/utils_test.rs @@ -1,13 +1,20 @@ +use ark_bls12_381::Fr; use rand::Rng; use starknet_types_core::curve::AffinePoint; use starknet_types_core::felt::Felt; +use crate::hints::hint_implementation::kzg::utils::{ + polynomial_coefficients_to_blob, + FIELD_ELEMENTS_PER_BLOB, +}; use crate::hints::hint_implementation::state_diff_encryption::utils::{ compute_starknet_public_keys, decrypt_state_diff, + decrypt_state_diff_from_blobs, encrypt_state_diff, encrypt_symmetric_key, }; +use crate::io::os_output_types::{PartialOsStateDiff, TryFromOutputIter}; #[test] fn test_encrypt_decrypt_roundtrip_random() { @@ -59,3 +66,87 @@ fn test_encrypt_decrypt_roundtrip_random() { assert_eq!(decrypted, state_diff); } } + +#[test] +fn test_decrypt_state_diff_from_blobs() { + let mut rng = rand::thread_rng(); + + // Unencrypted DA segment of output with `full_output=false` and `use_kzg_da=true`. + let da_segment = vec![ + Felt::from_hex("0x60001400000000010000000001000050002100000").unwrap(), + Felt::from_hex("0x1275130f95dda36bcbb6e9d28796c1d7e10b6e9fd5ed083e0ede4b12f613528") + .unwrap(), + Felt::from_hex("0x3291859abec6454596859fa7f51688b15d4b94d181eab1b0f6c44f070cee406") + .unwrap(), + Felt::from_hex("0x723973208639b7839ce298f7ffea61e3f9533872defd7abdb91023db4658812") + .unwrap(), + Felt::from_hex("0x7368e36c028305eeec3e9c87373deafc49e7fedc20692e8dcfb16f7d409ddf5") + .unwrap(), + Felt::from_hex("0x2833c37e53489206582153747f19c4385079c6a72d8252483c1b6e043ab8b5d") + .unwrap(), + Felt::from_hex("0x1ed09bead87c025bb625d13b19800").unwrap(), + Felt::from_hex("0x11d22c06ec4e6800").unwrap(), + Felt::from_hex("0x112022c046808f011c0240000001200041f4c3e487d20f9000280008005").unwrap(), + Felt::from_hex("0xc2001c801008c").unwrap(), + Felt::from_hex("0x2d756ff").unwrap(), + Felt::from_hex("0x38e90493bb0a8c19447d3f6").unwrap(), + ]; + + let original_state_diff = + PartialOsStateDiff::try_from_output_iter(&mut da_segment.clone().into_iter()) + .expect("Failed to parse DA segment into PartialOsStateDiff"); + + // Random number of keys. + let n_keys: usize = rng.gen_range(1..=5); + + // Generate private keys and corresponding public key x-coordinates. + let mut private_keys: Vec = Vec::with_capacity(n_keys); + let mut public_keys: Vec = Vec::with_capacity(n_keys); + for _ in 0..n_keys { + let private_key = Felt::from(rng.gen_range(1..=1_000_000)); + let public_key_x = (&AffinePoint::generator() * private_key).x(); + private_keys.push(private_key); + public_keys.push(public_key_x); + } + + // Generate SN private keys. + let mut sn_private_keys: Vec = Vec::with_capacity(n_keys); + for _ in 0..n_keys { + let sn_priv_scalar: u64 = rng.gen_range(1..=1_000_000); + sn_private_keys.push(Felt::from(sn_priv_scalar)); + } + + // Random symmetric key. + let symmetric_key = Felt::from_bytes_be(&rng.gen::<[u8; 32]>()); + + // Encrypt the DA segment + let encrypted_state_diff = encrypt_state_diff(symmetric_key, &da_segment); + let encrypted_symmetric_key = + encrypt_symmetric_key(&sn_private_keys, &public_keys, symmetric_key); + let sn_public_keys = compute_starknet_public_keys(&sn_private_keys); + + // Build full encrypted DA segment. + let full_da_segment: Vec = [Felt::from(n_keys)] + .into_iter() + .chain(sn_public_keys) + .chain(encrypted_symmetric_key) + .chain(encrypted_state_diff) + .collect(); + + // Convert to blobs. + let da_segment_fr: Vec = + full_da_segment.into_iter().map(|felt| Fr::from(felt.to_biguint())).collect(); + + let blobs: Vec> = da_segment_fr + .chunks(FIELD_ELEMENTS_PER_BLOB) + .map(|chunk| polynomial_coefficients_to_blob(chunk.to_vec()).unwrap()) + .collect(); + + let decrypted_state_diff = decrypt_state_diff_from_blobs(blobs, private_keys[0], 0) + .expect("Failed to decrypt and parse state diff from blobs"); + + assert_eq!( + decrypted_state_diff, original_state_diff, + "Decrypted state diff should match original" + ); +} diff --git a/crates/starknet_os/src/hints/vars.rs b/crates/starknet_os/src/hints/vars.rs index 155d1c79ecf..43cac45a399 100644 --- a/crates/starknet_os/src/hints/vars.rs +++ b/crates/starknet_os/src/hints/vars.rs @@ -148,6 +148,7 @@ define_string_enum! { (CompiledClassFacts), (CompiledClassHash), (CompressedDst), + (CompressedEnd), (CompressedStart), (CompressStateUpdates), (ConstructorCalldata), diff --git a/crates/starknet_os/src/lib.rs b/crates/starknet_os/src/lib.rs index c093ec17abb..f9706937650 100644 --- a/crates/starknet_os/src/lib.rs +++ b/crates/starknet_os/src/lib.rs @@ -5,6 +5,7 @@ pub mod hint_processor; pub mod hints; pub mod io; pub mod metrics; +pub mod opcode_instances; pub mod runner; pub mod syscall_handler_utils; #[cfg(any(test, feature = "testing"))] diff --git a/crates/starknet_os/src/metrics.rs b/crates/starknet_os/src/metrics.rs index ef359b25623..4680008ab54 100644 --- a/crates/starknet_os/src/metrics.rs +++ b/crates/starknet_os/src/metrics.rs @@ -6,6 +6,7 @@ use cairo_vm::vm::runners::cairo_runner::{CairoRunner, ExecutionResources}; use serde::Serialize; use crate::hint_processor::snos_hint_processor::SnosHintProcessor; +use crate::opcode_instances::{get_opcode_instances, OpcodeInstanceCounts}; #[derive(Debug, Serialize)] pub struct ProgramRunInfo { @@ -32,6 +33,7 @@ pub struct OsMetrics { pub deprecated_syscall_usages: Vec, pub run_info: ProgramRunInfo, pub execution_resources: ExecutionResources, + pub opcode_instances: OpcodeInstanceCounts, } #[derive(Debug, Serialize)] @@ -52,6 +54,7 @@ impl OsMetrics { .get_deprecated_syscall_usages(), run_info: ProgramRunInfo::new(runner), execution_resources: runner.get_execution_resources()?, + opcode_instances: get_opcode_instances(runner), }) } } diff --git a/crates/starknet_os/src/opcode_instances.rs b/crates/starknet_os/src/opcode_instances.rs new file mode 100644 index 00000000000..d8bdd16c860 --- /dev/null +++ b/crates/starknet_os/src/opcode_instances.rs @@ -0,0 +1,129 @@ +use cairo_vm::types::relocatable::MaybeRelocatable; +use cairo_vm::vm::runners::cairo_runner::CairoRunner; +use serde::Serialize; +use starknet_types_core::felt::Felt; + +fn instruction_to_u128(felt: &Felt) -> Result { + let bytes: [u8; 32] = felt.to_bytes_be(); + assert!(bytes[0..16] == [0; 16], "Instruction should fit in 128 bits"); + let u128_bytes: [u8; 16] = bytes[16..32].try_into().unwrap(); + Ok(u128::from_be_bytes(u128_bytes)) +} + +#[derive(Debug, PartialEq, Eq)] +enum OpcodeExt { + Stone, // 0 + Blake, // 1 + BlakeFinalize, // 2 + QM31Operation, // 3 + Unknown, +} + +#[derive(Debug, Serialize)] +pub struct OpcodeInstanceCounts { + pub blake_opcode_count: usize, +} + +/// Decode an encoded instruction (u128) into its components. +fn decode_instruction(mut encoded: u128) -> (bool, bool, bool, OpcodeExt) { + // Skip the 3 offsets (3 * 16 = 48 bits). + encoded >>= 48; + + // Read flags in the correct order (LSB-first). + let _dst_base_fp = (encoded & 1) != 0; + encoded >>= 1; + let _op0_base_fp = (encoded & 1) != 0; + encoded >>= 1; + + let op_1_imm = (encoded & 1) != 0; + encoded >>= 1; + let op_1_base_fp = (encoded & 1) != 0; + encoded >>= 1; + let op_1_base_ap = (encoded & 1) != 0; + encoded >>= 1; + + let res_add = (encoded & 1) != 0; + encoded >>= 1; + let res_mul = (encoded & 1) != 0; + encoded >>= 1; + let pc_update_jump = (encoded & 1) != 0; + encoded >>= 1; + let pc_update_jump_rel = (encoded & 1) != 0; + encoded >>= 1; + let pc_update_jnz = (encoded & 1) != 0; + encoded >>= 1; + let ap_update_add = (encoded & 1) != 0; + encoded >>= 1; + let _ap_update_add_1 = (encoded & 1) != 0; + encoded >>= 1; + let opcode_call = (encoded & 1) != 0; + encoded >>= 1; + let opcode_ret = (encoded & 1) != 0; + encoded >>= 1; + let opcode_assert_eq = (encoded & 1) != 0; + encoded >>= 1; + + let unwanted_flags = op_1_imm + || res_add + || res_mul + || pc_update_jump + || pc_update_jump_rel + || pc_update_jnz + || ap_update_add + || opcode_call + || opcode_ret + || opcode_assert_eq; + + // Remaining bits are the opcode extension. + let opcode_extension = match encoded { + 0 => OpcodeExt::Stone, + 1 => OpcodeExt::Blake, + 2 => OpcodeExt::BlakeFinalize, + 3 => OpcodeExt::QM31Operation, + _ => OpcodeExt::Unknown, + }; + + (unwanted_flags, op_1_base_fp, op_1_base_ap, opcode_extension) +} + +/// Check if a decoded instruction is a Blake opcode. +fn is_blake_opcode( + unwanted_flags: bool, + op_1_base_fp: bool, + op_1_base_ap: bool, + opcode_extension: &OpcodeExt, +) -> bool { + // Blake opcodes have: + // - opcode_extension is Blake or BlakeFinalize + // - no unwanted flags (op_1_imm, res_add, res_mul, etc.) + // - the data we hash is stored on ap or fp exclusively (XOR) + matches!(opcode_extension, OpcodeExt::Blake | OpcodeExt::BlakeFinalize) + && !unwanted_flags + && (op_1_base_fp ^ op_1_base_ap) +} + +/// Count Blake opcodes from the Cairo runner's execution trace. +pub fn get_opcode_instances(runner: &CairoRunner) -> OpcodeInstanceCounts { + let Ok(info) = runner.get_prover_input_info() else { + eprintln!("Failed to get prover input info. Returning zero count."); + return OpcodeInstanceCounts { blake_opcode_count: 0 }; + }; + + let count = info + .relocatable_trace + .iter() + .filter(|entry| { + (|| { + let seg = usize::try_from(entry.pc.segment_index).ok()?; + let value = info.relocatable_memory.get(seg)?.get(entry.pc.offset)?.as_ref()?; + let MaybeRelocatable::Int(felt) = value else { return None }; + let instr = instruction_to_u128(felt).ok()?; + let (unwanted_flags, op1_fp, op1_ap, opcode_ext) = decode_instruction(instr); + Some(is_blake_opcode(unwanted_flags, op1_fp, op1_ap, &opcode_ext)) + })() + .unwrap_or(false) + }) + .count(); + + OpcodeInstanceCounts { blake_opcode_count: count } +} diff --git a/crates/starknet_os/src/runner.rs b/crates/starknet_os/src/runner.rs index 4cbf7091bab..0ca20f44e71 100644 --- a/crates/starknet_os/src/runner.rs +++ b/crates/starknet_os/src/runner.rs @@ -51,6 +51,7 @@ fn run_program<'a, HP: HintProcessor + CommonHintProcessor<'a>>( hint_processor: &mut HP, ) -> Result { // Init CairoRunConfig. + // TODO(Einat): Set trace_enabled to false once blake opcodes are counted in the VM. let cairo_run_config = CairoRunConfig { layout, relocate_mem: true, trace_enabled: true, ..Default::default() }; let allow_missing_builtins = cairo_run_config.allow_missing_builtins.unwrap_or(false); diff --git a/deployments/images/papyrus/Dockerfile b/deployments/images/papyrus/Dockerfile deleted file mode 100644 index 6fc0e21636a..00000000000 --- a/deployments/images/papyrus/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# syntax = devthefuture/dockerfile-x - -INCLUDE deployments/images/base/Dockerfile - - -# Compile the papyrus_node crate in release mode, ensuring dependencies are locked. -FROM base AS builder -COPY . . -RUN rustup toolchain install -RUN cargo build --release --package papyrus_node - -FROM ubuntu:24.04 as final_stage - -ENV ID=1001 -WORKDIR /app -COPY --from=builder /target/release/papyrus_node /app/target/release/papyrus_node -COPY --from=builder /usr/bin/tini /usr/bin/tini - -COPY config/papyrus config/papyrus - -# Create a new user "papyrus". -RUN set -ex; \ - groupadd --gid ${ID} papyrus; \ - useradd --gid ${ID} --uid ${ID} --comment "" --create-home --home-dir /app papyrus; \ - chown -R papyrus:papyrus /app - -# Expose RPC and monitoring ports. -EXPOSE 8080 8081 - -# Switch to the new user. -USER ${ID} - -# Set the entrypoint to use tini to manage the process. -ENTRYPOINT ["tini", "--", "/app/target/release/papyrus_node"] diff --git a/deployments/monitoring/local/docker-compose.yml b/deployments/monitoring/local/docker-compose.yml index 6550912aec3..d1d908361eb 100644 --- a/deployments/monitoring/local/docker-compose.yml +++ b/deployments/monitoring/local/docker-compose.yml @@ -31,7 +31,7 @@ services: - RUST_BACKTRACE=${RUST_BACKTRACE} entrypoint: "/bin/bash -c" command: > - "./target/debug/sequencer_node_setup --output-base-dir ./output --data-prefix-path /data --n-distributed 0 --n-consolidated 1; + "./target/debug/sequencer_node_setup --output-base-dir ./output --data-prefix-path /data --n-distributed 0 --n-hybrid 0 --n-consolidated 1; cp -r ./output/data/* /data; cp -r ./output/configs/* /config" volumes: - data:/data diff --git a/deployments/monitoring/src/builders/dashboard_builder.py b/deployments/monitoring/src/builders/dashboard_builder.py index fd20db1bc79..440e4703553 100755 --- a/deployments/monitoring/src/builders/dashboard_builder.py +++ b/deployments/monitoring/src/builders/dashboard_builder.py @@ -9,6 +9,8 @@ from common.helpers import EnvironmentName, env_to_gcp_project_name, get_logger from urllib.parse import quote +MAX_ALLOWED_JSON_SIZE = 1024 * 1024 # 1MB + def create_grafana_panel(panel: dict, panel_id: int, y_position: int, x_position: int) -> dict: exprs = panel["exprs"] @@ -81,7 +83,6 @@ def create_grafana_panel(panel: dict, panel_id: int, y_position: int, x_position } ], }, - "options": {"showPercentChange": show_percent_change}, "links": ([{"url": link, "title": "GCP Logs", "targetBlank": True}]), "transformations": [ # Renames labels of the form {label="value"} to just "value" @@ -99,6 +100,12 @@ def create_grafana_panel(panel: dict, panel_id: int, y_position: int, x_position if thresholds: grafana_panel["fieldConfig"]["defaults"]["color"] = {"mode": "thresholds"} + if panel["type"] == "stat": + grafana_panel["options"] = { + "textMode": "value_and_name", + "showPercentChange": show_percent_change, + } + return grafana_panel @@ -226,8 +233,10 @@ def dashboard_builder(args: argparse.Namespace) -> None: if args.out_dir: output_dir = f"{args.out_dir}/dashboards" os.makedirs(output_dir, exist_ok=True) - with open(dashboard_file_name(output_dir, dashboard_name), "w") as f: - json.dump(dashboard, f, indent=4) + json_data = json.dumps(dashboard, indent=1, ensure_ascii=False) + assert len(json_data) < MAX_ALLOWED_JSON_SIZE, "Grafana dashboard JSON is too large" + with open(dashboard_file_name(output_dir, dashboard_name), "w", encoding="utf-8") as f: + f.write(json_data) if not args.dry_run: upload_dashboards_local(dashboard=dashboard) diff --git a/deployments/papyrus/helm/CI/values.yaml b/deployments/papyrus/helm/CI/values.yaml deleted file mode 100644 index d60202e9501..00000000000 --- a/deployments/papyrus/helm/CI/values.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Default values for a papyrus ci deployment. - -rustLogLevel: "debug" - -deployment: - image: - tag: dev - -pvc: - size: 1Gi - storageClass: standard-rwo -# Configure Ingress. -# ingress: -# enabled: true -# type: gce -# name: ssl-proxy -# pathType: ImplementationSpecific -# annotations: | -# kubernetes.io/ingress.class: nginx -# kubernetes.io/tls-acme: "true" -# nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - -grafanadashboard: - enabled: true diff --git a/deployments/papyrus/helm/Chart.yaml b/deployments/papyrus/helm/Chart.yaml deleted file mode 100644 index 73891673d6b..00000000000 --- a/deployments/papyrus/helm/Chart.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v2 -name: papyrus -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 diff --git a/deployments/papyrus/helm/Monitoring/.gitignore b/deployments/papyrus/helm/Monitoring/.gitignore deleted file mode 100644 index 5e7d2734cfc..00000000000 --- a/deployments/papyrus/helm/Monitoring/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/deployments/papyrus/helm/README.md b/deployments/papyrus/helm/README.md deleted file mode 100644 index aa73e518a1b..00000000000 --- a/deployments/papyrus/helm/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Papyrus helm chart installation - -## Usage - -```bash -helm upgrade --install `release_name` deployments/helm/ \ ---namespace `namespace_name` --create-namespace \ ---set ingress.host=`ingress_hostname` -``` diff --git a/deployments/papyrus/helm/backup-values.yaml b/deployments/papyrus/helm/backup-values.yaml deleted file mode 100644 index 6c94c11a46f..00000000000 --- a/deployments/papyrus/helm/backup-values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Default values for a papyrus-backup deployment. - -# The verbosity level of logs ("debug", "info", "error", etc.) -rustLogLevel: "papyrus=DEBUG" - -# The Docker image (including any tag and repository name) -deployment: - image: - repository: us.gcr.io/starkware-dev/papyrus-backup - tag: 0.2.0 -services: [] -# Persistent volume claim variables for a papyrus pod. -pvc: - size: 1000Gi - storageClass: premium-rwo - -backup: - enabled: true - aws: - s3BucketName: papyrus-backup - s3BucketRegion: us-east-2 diff --git a/deployments/papyrus/helm/config/example.json b/deployments/papyrus/helm/config/example.json deleted file mode 100644 index 71460ee199d..00000000000 --- a/deployments/papyrus/helm/config/example.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "base_layer_url": "", - "network.port": 10000, - "storage.db_config.path_prefix": "./data", - "network.#is_none": false, - "storage.scope": "FullArchive", - "collect_metrics": true -} diff --git a/deployments/papyrus/helm/files/backup.sh b/deployments/papyrus/helm/files/backup.sh deleted file mode 100644 index 4a016b98111..00000000000 --- a/deployments/papyrus/helm/files/backup.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env sh -set -x -if [ -z "${ADDITIONAL_HEADER}" ]; then - ADDITIONAL_ARGS="" -else - ADDITIONAL_ARGS="--http_headers=${ADDITIONAL_HEADER}" -fi - -if [ -n "${CONCURRENT_REQUESTS}" ]; then - # temporary workaround for an internal papyrus memory issue - sed -i "s/concurrent_requests: 10/concurrent_requests: $CONCURRENT_REQUESTS/g" /app/config/config.yaml -fi - -RUN_CMD="/app/target/release/papyrus_node --config_file=/app/config/presets/${PRESET} ${ADDITIONAL_ARGS}" - -while true; do - # start papyrus and save the pid - sh -c "$RUN_CMD" & - PAPYRUS_PID="$!" - - sleep "$SLEEP_INTERVAL" - - # stop papyrus - kill -15 "$PAPYRUS_PID" - sleep 5s - - TS=$(date +%s) - if [ "$COMPRESS_BACKUP" = true ]; then - # compress file, upload compressed file and delete the compressed file - cd "/app/data/$CHAIN_ID" || exit 1 - TAR_FILE_NAME="$TS.tar.gz" - tar -czvf "$TAR_FILE_NAME" mdbx.dat - aws s3 cp "$TAR_FILE_NAME" "s3://$S3_BUCKET_NAME/$CHAIN_ID/$PAPYRUS_VERSION/$TAR_FILE_NAME" - rm "$TAR_FILE_NAME" - cd /app || exit 1 - else - # upload db file to s3 - aws s3 cp "/app/data/$CHAIN_ID/mdbx.dat" "s3://$S3_BUCKET_NAME/$CHAIN_ID/$PAPYRUS_VERSION/$TS.dat" - fi -done diff --git a/deployments/papyrus/helm/reference/template.yaml b/deployments/papyrus/helm/reference/template.yaml deleted file mode 100644 index 8390b2f2946..00000000000 --- a/deployments/papyrus/helm/reference/template.yaml +++ /dev/null @@ -1,164 +0,0 @@ ---- -# Source: papyrus/templates/configmap-env.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: papyrus-env - labels: - helm.sh/chart: papyrus-0.1.0 - app: papyrus - app.kubernetes.io/name: papyrus - app.kubernetes.io/managed-by: Helm -data: - RUST_LOG: info - PRESET: mainnet.json - CONCURRENT_REQUESTS: "50" ---- -# Source: papyrus/templates/configmap.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: papyrus-config - labels: - helm.sh/chart: papyrus-0.1.0 - app: papyrus - app.kubernetes.io/name: papyrus - app.kubernetes.io/managed-by: Helm -data: - config.json: |- - {} ---- -# Source: papyrus/templates/pvc.yaml -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: papyrus-data - labels: - helm.sh/chart: papyrus-0.1.0 - app: papyrus - app.kubernetes.io/name: papyrus - app.kubernetes.io/managed-by: Helm -spec: - storageClassName: - accessModes: - - ReadWriteOnce - volumeMode: Filesystem - resources: - requests: - storage: "512Gi" ---- -# Source: papyrus/templates/service-p2p.yaml -apiVersion: v1 -kind: Service -metadata: - name: papyrus-p2p - labels: - helm.sh/chart: papyrus-0.1.0 - app: papyrus - app.kubernetes.io/name: papyrus - app.kubernetes.io/managed-by: Helm -spec: - selector: - app.kubernetes.io/name: papyrus - type: ClusterIP - ports: - - name: p2p - port: 10000 - protocol: TCP - targetPort: p2p ---- -# Source: papyrus/templates/service.yaml -apiVersion: v1 -kind: Service -metadata: - name: papyrus - labels: - helm.sh/chart: papyrus-0.1.0 - app: papyrus - app.kubernetes.io/name: papyrus - app.kubernetes.io/managed-by: Helm -spec: - selector: - app.kubernetes.io/name: papyrus - type: ClusterIP - ports: - - name: rpc - port: 8080 - protocol: TCP - targetPort: rpc - - name: monitoring - port: 8081 - protocol: TCP - targetPort: monitoring ---- -# Source: papyrus/templates/deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: papyrus - namespace: idan-papyrus-p2p-dnsaddr-test - labels: - helm.sh/chart: papyrus-0.1.0 - app: papyrus - app.kubernetes.io/name: papyrus - app.kubernetes.io/managed-by: Helm -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: papyrus - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - maxSurge: 1 - template: - metadata: - annotations: - prometheus.io/scrape: "true" - prometheus.io/path: "/monitoring/metrics" - prometheus.io/port: "8081" - labels: - app: papyrus - app.kubernetes.io/name: papyrus - spec: - securityContext: - fsGroup: 1000 - volumes: - - name: data - persistentVolumeClaim: - claimName: papyrus-data - - name: config-volume - configMap: - name: papyrus-config - containers: - - name: papyrus - image: "ghcr.io/starkware-libs/sequencer/papyrus:0.4.0" - imagePullPolicy: Always - resources: - limits: - cpu: "1" - memory: 1Gi - requests: - cpu: "500m" - memory: 1Gi - args: - - --config_file - - /app/config/papyrus/presets/mainnet.json,/app/config/papyrus/custom/config.json - - ports: - - containerPort: 8080 - name: rpc - - containerPort: 8081 - name: monitoring - - containerPort: 10000 - name: p2p - volumeMounts: - - name: data - mountPath: /app/data - - name: config-volume - mountPath: /app/config/papyrus/custom/config.json - subPath: config.json - envFrom: - - configMapRef: - name: papyrus-config diff --git a/deployments/papyrus/helm/templates/_helpers.tpl b/deployments/papyrus/helm/templates/_helpers.tpl deleted file mode 100644 index 9b6c5636481..00000000000 --- a/deployments/papyrus/helm/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "papyrus.name" -}} -{{- default .Release.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "papyrus.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "papyrus.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "papyrus.labels" -}} -helm.sh/chart: {{ include "papyrus.chart" . }} -app: {{ include "papyrus.name" . }} -{{ include "papyrus.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "papyrus.selectorLabels" -}} -app.kubernetes.io/name: {{ include "papyrus.name" . }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "papyrus.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "papyrus.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/aws-creds-secret.yaml b/deployments/papyrus/helm/templates/aws-creds-secret.yaml deleted file mode 100644 index 328f3e0354f..00000000000 --- a/deployments/papyrus/helm/templates/aws-creds-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.backup.enabled }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "papyrus.name" . }}-aws-creds - labels: - {{- include "papyrus.labels" . | nindent 4 }} -data: - AWS_ACCESS_KEY_ID: {{ .Values.backup.aws.accessKeyId | b64enc }} - AWS_SECRET_ACCESS_KEY: {{ .Values.backup.aws.secretAccessKey | b64enc }} - AWS_DEFAULT_REGION: {{ .Values.backup.aws.s3BucketRegion | b64enc }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/configmap-env.yaml b/deployments/papyrus/helm/templates/configmap-env.yaml deleted file mode 100644 index 5e5da60528a..00000000000 --- a/deployments/papyrus/helm/templates/configmap-env.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "papyrus.name" . }}-env - labels: - {{- include "papyrus.labels" . | nindent 4 }} -data: - RUST_LOG: {{ .Values.rustLogLevel }} - PRESET: {{ .Values.starknet.preset }} - CONCURRENT_REQUESTS: {{ .Values.node.concurrentFgRequests | quote }} - {{- if .Values.starknet.additionalHeaders }} - ADDITIONAL_HEADER: {{ .Values.starknet.additionalHeaders }} - {{- end }} - {{- if .Values.backup.enabled }} - SLEEP_INTERVAL: {{ .Values.backup.sleepInterval }} - S3_BUCKET_NAME: {{ .Values.backup.aws.s3BucketName }} - PAPYRUS_VERSION: {{ .Values.image.tag | quote }} - COMPRESS_BACKUP: {{ .Values.backup.compress | quote }} - {{- end }} diff --git a/deployments/papyrus/helm/templates/configmap-run.yaml b/deployments/papyrus/helm/templates/configmap-run.yaml deleted file mode 100644 index ffbe00b7f67..00000000000 --- a/deployments/papyrus/helm/templates/configmap-run.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.backup.enabled }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "papyrus.name" . }}-run - labels: - {{- include "papyrus.labels" . | nindent 4 }} -data: - backup.sh: | - {{- tpl (.Files.Get "files/backup.sh") . | nindent 4 }} -{{- end }} - diff --git a/deployments/papyrus/helm/templates/configmap.yaml b/deployments/papyrus/helm/templates/configmap.yaml deleted file mode 100644 index bff1f5b8c38..00000000000 --- a/deployments/papyrus/helm/templates/configmap.yaml +++ /dev/null @@ -1,19 +0,0 @@ - -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "papyrus.name" . }}-config - labels: - {{- include "papyrus.labels" . | nindent 4 }} -data: - config.json: |- - {{- if .Values.deployment.configFile }} - {{- $filePath := printf "config/%s" .Values.deployment.configFile -}} - {{- if not (.Files.Get $filePath) -}} - {{- fail (printf "Error: The file %s does not exist in the chart." $filePath) -}} - {{- else }} - {{ .Files.Get $filePath | nindent 4 }} - {{- end }} - {{- else }} - {} - {{- end }} diff --git a/deployments/papyrus/helm/templates/deployment.yaml b/deployments/papyrus/helm/templates/deployment.yaml deleted file mode 100644 index 24ff73688fa..00000000000 --- a/deployments/papyrus/helm/templates/deployment.yaml +++ /dev/null @@ -1,152 +0,0 @@ -{{- if eq .Values.deployment.type "deployment" }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "papyrus.name" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "papyrus.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - {{- include "papyrus.selectorLabels" . | nindent 6 }} - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - maxSurge: 1 - template: - metadata: - annotations: - {{- if .Values.deployment.annotations }} - {{ toYaml .Values.deployment.annotations | nindent 8 }} - {{- end}} - {{- if .Values.service.ports.monitoring.enabled }} - prometheus.io/scrape: "true" - prometheus.io/path: "/monitoring/metrics" - prometheus.io/port: {{ .Values.service.ports.monitoring.port | quote }} - {{- end }} - labels: - app: papyrus - {{- include "papyrus.selectorLabels" . | nindent 8 }} - spec: - securityContext: - fsGroup: 1000 - volumes: - - name: data - persistentVolumeClaim: - claimName: {{ template "papyrus.name" . }}-data - - name: config-volume - configMap: - name: {{ template "papyrus.name" . }}-config - {{- if .Values.backup.enabled }} - - name: run - configMap: - name: {{ template "papyrus.name" . }}-run - defaultMode: 0777 - {{- end }} - {{- with .Values.deployment.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.deployment.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: {{ template "papyrus.name" . }} - image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" - imagePullPolicy: {{ .Values.deployment.pullPolicy }} - resources: - limits: - cpu: {{ .Values.deployment.resources.limits.cpu | quote }} - memory: {{ .Values.deployment.resources.limits.memory }} - requests: - cpu: {{ .Values.deployment.resources.requests.cpu | quote}} - memory: {{ .Values.deployment.resources.requests.memory }} - {{- if not .Values.backup.enabled }} - {{- with .Values.deployment.env }} - env: - {{- toYaml . | nindent 10 }} - {{- end }} - args: - - --config_file - - /app/config/papyrus/presets/{{ .Values.starknet.preset }},/app/config/papyrus/custom/config.json - {{ range $key, $value := .Values.deployment.extraArgs }} - {{- if $value }} - - --{{ $key }} - - {{ $value | quote }} - {{- else }} - - --{{ $key }} - {{- end }} - {{ end }} - ports: - {{- if .Values.service.ports.rpc.enabled }} - - containerPort: {{ .Values.service.ports.rpc.port }} - name: rpc - {{- end }} - {{- if .Values.service.ports.monitoring.enabled }} - - containerPort: {{ .Values.service.ports.monitoring.port }} - name: monitoring - {{- end }} - {{- if .Values.p2pService.enabled }} - - containerPort: {{ .Values.p2pService.port }} - name: p2p - {{- end }} - volumeMounts: - - name: data - mountPath: /app/data - - name: config-volume - mountPath: /app/config/papyrus/custom/config.json - subPath: config.json - envFrom: - - configMapRef: - name: {{ template "papyrus.name" . }}-config - {{- else }} - command: - - sh - - -c - - /app/run/backup.sh - volumeMounts: - - name: data - mountPath: /app/data - - name: config-volume - mountPath: /app/config/papyrus/custom/config.json - subPath: config.json - - name: run - mountPath: /app/run - envFrom: - - configMapRef: - name: {{ template "papyrus.name" . }}-env - - secretRef: - name: {{ template "papyrus.name" . }}-aws-creds - {{- end }} -{{- if or .Values.deployment.affinity .Values.deployment.podAntiAffinity }} - affinity: - {{- end }} - {{- with .Values.deployment.affinity }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- if eq .Values.deployment.podAntiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: {{ .Values.deployment.podAntiAffinityTopologyKey }} - labelSelector: - matchExpressions: - - {key: app.kubernetes.io/name, operator: In, values: [{{ template "papyrus.name" . }}]} - {{- else if eq .Values.deployment.podAntiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - topologyKey: {{ .Values.deployment.podAntiAffinityTopologyKey }} - labelSelector: - matchExpressions: - - {key: app.kubernetes.io/name, operator: In, values: [{{ template "papyrus.name" . }}]} - {{- end }} - {{- with .Values.deployment.topologySpreadConstraints }} - topologySpreadConstraints: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/grafana-alerts.yaml b/deployments/papyrus/helm/templates/grafana-alerts.yaml deleted file mode 100644 index e9234a53516..00000000000 --- a/deployments/papyrus/helm/templates/grafana-alerts.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.grafanaAlerts.enabled }} -apiVersion: integreatly.org/v1alpha1 -kind: GrafanaDashboard -metadata: - name: {{ template "papyrus.name" . }}-alerts - namespace: {{ .Release.Namespace | quote }} - labels: - app: grafana-dashboard -spec: - json: | - {{- (.Files.Get "Monitoring/grafana_alerts.json") | nindent 4 }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/grafana-dashboard.yaml b/deployments/papyrus/helm/templates/grafana-dashboard.yaml deleted file mode 100644 index 0b3c6d70df2..00000000000 --- a/deployments/papyrus/helm/templates/grafana-dashboard.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.grafanaDashboard.enabled }} -apiVersion: integreatly.org/v1alpha1 -kind: GrafanaDashboard -metadata: - name: {{ template "papyrus.name" . }}-dashboard - namespace: {{ .Release.Namespace | quote }} - labels: - app: grafana-dashboard -spec: - json: | - {{- (.Files.Get "Monitoring/grafana_dashboard.json") | nindent 4 }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/ingress.yaml b/deployments/papyrus/helm/templates/ingress.yaml deleted file mode 100644 index d0bf9935cc1..00000000000 --- a/deployments/papyrus/helm/templates/ingress.yaml +++ /dev/null @@ -1,36 +0,0 @@ -{{- if .Values.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: "{{ .Values.ingress.name }}-{{ template "papyrus.name" . }}" - namespace: {{ .Release.Namespace }} - labels: - {{- include "papyrus.labels" . | nindent 4 }} -{{- with .Values.ingress.annotations }} - annotations: - {{ toYaml . | nindent 4 }} -{{- end }} -spec: - rules: - - host: {{ .Values.ingress.host }} - http: - paths: - - backend: - service: - name: {{ template "papyrus.name" . }}-rpc - port: - number: {{ .Values.services.rpc.port }} - path: /rpc - pathType: {{ .Values.ingress.pathType }} - - backend: - service: - name: {{ template "papyrus.name" . }}-monitoring - port: - number: {{ .Values.services.monitoring.port }} - path: /monitoring - pathType: {{ .Values.ingress.pathType }} - tls: - - hosts: - - {{ .Values.ingress.host }} - secretName: {{ .Values.ingress.host }}-ssl-secret -{{- end }} diff --git a/deployments/papyrus/helm/templates/pdb.yaml b/deployments/papyrus/helm/templates/pdb.yaml deleted file mode 100644 index 517b5939bd1..00000000000 --- a/deployments/papyrus/helm/templates/pdb.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if .Values.deployment.podDisruptionBudget.enabled }} -{{- $pdbSpec := omit .Values.deployment.podDisruptionBudget "enabled" }} -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: {{ template "papyrus.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "papyrus.labels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "papyrus.matchLabels" . | nindent 6 }} - {{- toYaml $pdbSpec | nindent 2 }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/pvc.yaml b/deployments/papyrus/helm/templates/pvc.yaml deleted file mode 100644 index c8223b4f7de..00000000000 --- a/deployments/papyrus/helm/templates/pvc.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if eq .Values.deployment.type "deployment" }} -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ template "papyrus.name" . }}-data - labels: - {{- include "papyrus.labels" . | nindent 4 }} -spec: - storageClassName: {{ .Values.pvc.storageClass }} - accessModes: - - ReadWriteOnce - volumeMode: Filesystem - resources: - requests: - storage: {{ .Values.pvc.size | quote }} - {{- if .Values.pvc.restoreFromSnapshot.enabled }} - dataSource: - name: {{ .Values.pvc.restoreFromSnapshot.snapshotName }} - kind: VolumeSnapshot - apiGroup: snapshot.storage.k8s.io - {{- end }} -{{- end }} diff --git a/deployments/papyrus/helm/templates/service-p2p.yaml b/deployments/papyrus/helm/templates/service-p2p.yaml deleted file mode 100644 index 15c7e692715..00000000000 --- a/deployments/papyrus/helm/templates/service-p2p.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if and ( not .Values.backup.enabled ) .Values.p2pService.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ template "papyrus.name" . }}-p2p - labels: - {{- include "papyrus.labels" . | nindent 4 }} - annotations: - {{- if .Values.p2pService.annotations }} - {{ toYaml .Values.p2pService.annotations | nindent 4 }} - {{- end}} -spec: - selector: - {{- include "papyrus.selectorLabels" . | nindent 6 }} - type: {{ .Values.p2pService.type }} - {{- if and (eq .Values.p2pService.type "ClusterIP") .Values.p2pService.clusterIP }} - clusterIP: {{ .Values.p2pService.clusterIP }} - {{- end }} - {{- if and (eq .Values.p2pService.type "LoadBalancer") .Values.p2pService.loadBalancerIP }} - loadBalancerIP: {{ .Values.p2pService.loadBalancerIP }} - {{- end }} - ports: - - name: p2p - port: {{ .Values.p2pService.port }} - protocol: {{ .Values.p2pService.protocol }} - targetPort: p2p -{{- end }} diff --git a/deployments/papyrus/helm/templates/service.yaml b/deployments/papyrus/helm/templates/service.yaml deleted file mode 100644 index 1b073cf3f0f..00000000000 --- a/deployments/papyrus/helm/templates/service.yaml +++ /dev/null @@ -1,26 +0,0 @@ -{{- if not .Values.backup.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ template "papyrus.name" . }} - labels: - {{- include "papyrus.labels" . | nindent 4 }} -spec: - selector: - {{- include "papyrus.selectorLabels" . | nindent 6 }} - type: {{ .Values.service.type }} - ports: - {{- if and .Values.service.ports.rpc .Values.service.ports.rpc.enabled }} - - name: rpc - port: {{ .Values.service.ports.rpc.port }} - protocol: {{ .Values.service.ports.rpc.protocol }} - targetPort: rpc - {{- end }} - {{- if and .Values.service.ports.monitoring .Values.service.ports.monitoring.enabled }} - - name: monitoring - port: {{ .Values.service.ports.monitoring.port }} - protocol: {{ .Values.service.ports.monitoring.protocol }} - targetPort: monitoring - {{- end }} -{{- end }} \ No newline at end of file diff --git a/deployments/papyrus/helm/templates/statefulset.yaml b/deployments/papyrus/helm/templates/statefulset.yaml deleted file mode 100644 index e5ed6e5bcb0..00000000000 --- a/deployments/papyrus/helm/templates/statefulset.yaml +++ /dev/null @@ -1,161 +0,0 @@ -{{- if eq .Values.deployment.type "statefulset" }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ template "papyrus.name" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "papyrus.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - {{- include "papyrus.selectorLabels" . | nindent 6 }} - updateStrategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - template: - metadata: - annotations: - {{- if .Values.deployment.annotations }} - {{ toYaml .Values.deployment.annotations | nindent 8 }} - {{- end}} - {{- if .Values.service.ports.monitoring.enabled }} - prometheus.io/scrape: "true" - prometheus.io/path: "/monitoring/metrics" - prometheus.io/port: {{ .Values.service.ports.monitoring.port | quote }} - {{- end }} - labels: - app: papyrus - {{- include "papyrus.selectorLabels" . | nindent 8 }} - spec: - securityContext: - fsGroup: 1000 - volumes: - - name: config-volume - configMap: - name: {{ template "papyrus.name" . }}-config - {{- if .Values.backup.enabled }} - - name: run - configMap: - name: {{ template "papyrus.name" . }}-run - defaultMode: 0777 - {{- end }} - {{- with .Values.deployment.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.deployment.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: {{ template "papyrus.name" . }} - image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}" - imagePullPolicy: {{ .Values.deployment.pullPolicy }} - resources: - limits: - cpu: {{ .Values.deployment.resources.limits.cpu | quote }} - memory: {{ .Values.deployment.resources.limits.memory }} - requests: - cpu: {{ .Values.deployment.resources.requests.cpu | quote}} - memory: {{ .Values.deployment.resources.requests.memory }} - {{- if not .Values.backup.enabled }} - {{- with .Values.deployment.env }} - env: - {{- toYaml . | nindent 10 }} - {{- end }} - args: - - --config_file - - /app/config/papyrus/presets/{{ .Values.starknet.preset }},/app/config/papyrus/custom/config.json - {{ range $key, $value := .Values.deployment.extraArgs }} - {{- if $value }} - - --{{ $key }} - - {{ $value | quote }} - {{- else }} - - --{{ $key }} - {{- end }} - {{ end }} - ports: - {{- if .Values.service.ports.rpc.enabled }} - - containerPort: {{ .Values.service.ports.rpc.port }} - name: rpc - {{- end }} - {{- if .Values.service.ports.monitoring.enabled }} - - containerPort: {{ .Values.service.ports.monitoring.port }} - name: monitoring - {{- end }} - {{- if .Values.p2pService.enabled }} - - containerPort: {{ .Values.p2pService.port }} - name: p2p - {{- end }} - volumeMounts: - - name: {{ template "papyrus.name" . }}-data - mountPath: /app/data - - name: config-volume - mountPath: /app/config/papyrus/custom/config.json - subPath: config.json - envFrom: - - configMapRef: - name: {{ template "papyrus.name" . }}-env - {{- else }} - command: - - sh - - -c - - /app/run/backup.sh - volumeMounts: - - name: {{ template "papyrus.name" . }}-data - mountPath: /app/data - - name: config-volume - mountPath: /app/config/papyrus/custom/config.json - subPath: config.json - - name: run - mountPath: /app/run - envFrom: - - configMapRef: - name: {{ template "papyrus.name" . }}-env - - secretRef: - name: {{ template "papyrus.name" . }}-aws-creds - {{- end }} - {{- if or .Values.deployment.affinity .Values.deployment.podAntiAffinity }} - affinity: - {{- end }} - {{- with .Values.deployment.affinity }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- if eq .Values.deployment.podAntiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: {{ .Values.deployment.podAntiAffinityTopologyKey }} - labelSelector: - matchExpressions: - - {key: app.kubernetes.io/name, operator: In, values: [{{ template "papyrus.name" . }}]} - {{- else if eq .Values.deployment.podAntiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - topologyKey: {{ .Values.deployment.podAntiAffinityTopologyKey }} - labelSelector: - matchExpressions: - - {key: app.kubernetes.io/name, operator: In, values: [{{ template "papyrus.name" . }}]} - {{- end }} - {{- with .Values.deployment.topologySpreadConstraints }} - topologySpreadConstraints: - {{- toYaml . | nindent 8 }} - {{- end }} - volumeClaimTemplates: - - metadata: - name: {{ template "papyrus.name" . }}-data - labels: - {{- include "papyrus.labels" . | nindent 8 }} - spec: - storageClassName: {{ .Values.pvc.storageClass }} - accessModes: - - "ReadWriteOnce" - volumeMode: Filesystem - resources: - requests: - storage: {{ .Values.pvc.size | quote }} -{{- end }} diff --git a/deployments/papyrus/helm/values.yaml b/deployments/papyrus/helm/values.yaml deleted file mode 100644 index 4fba11c68e7..00000000000 --- a/deployments/papyrus/helm/values.yaml +++ /dev/null @@ -1,171 +0,0 @@ -# Default values for a papyrus deployment. - -# The verbosity level of logs ("debug", "info", "error", etc.) -rustLogLevel: "info" - -node: - # Number of concurrent requests to the SN feeder gateway - concurrentFgRequests: 50 - -# Ethereum node URL. A value for this variable is mandatory. -base_layer_node_url: - -starknet: - # possible values: "mainnet.json, sepolia_testnet" and "sepolia_integration". - preset: mainnet.json - -deployment: - # Supported values: deployment, statefulset - type: deployment - - # The container image - image: - repository: ghcr.io/starkware-libs/sequencer/papyrus - tag: 0.4.0 - - # The name of the papyrus config file. For example: my-config.json - # The config file must be placed under "config" folder in the chart root folder. - # ./ - # templates/ - # ---> config/ - # Chart.yaml - # values.yaml - configFile: - - # The container's pullPolicy - pullPolicy: Always - - # Set pod annotations - annotations: {} - - # Set deployment nodeSelector - nodeSelector: {} - - # Set deployment tolerations - tolerations: [] - # - key: "key1" - # operator: "Equal" - # value: "value1" - # effect: "NoSchedule" - - affinity: {} - - ## Pod anti-affinity can prevent the scheduler from placing papyrus server replicas on the same node. - ## The value "soft" means that the scheduler should *prefer* to not schedule two replica pods onto the same node but no guarantee is provided. - ## The value "hard" means that the scheduler is *required* to not schedule two replica pods onto the same node. - ## The default value "" will disable pod anti-affinity so that no anti-affinity rules will be configured (unless set in `deployment.affinity`). - ## - podAntiAffinity: "" - - ## If anti-affinity is enabled sets the topologyKey to use for anti-affinity. - ## This can be changed to, for example, failure-domain.beta.kubernetes.io/zone - ## - podAntiAffinityTopologyKey: failure-domain.beta.kubernetes.io/zone - - ## Pod topology spread constraints - ## ref. https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ - topologySpreadConstraints: [] - - ## PodDisruptionBudget settings - ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ - ## - podDisruptionBudget: - enabled: false - maxUnavailable: 1 - # minAvailable: 1 - ## unhealthyPodEvictionPolicy is available since 1.27.0 (beta) - ## https://kubernetes.io/docs/tasks/run-application/configure-pdb/#unhealthy-pod-eviction-policy - # unhealthyPodEvictionPolicy: IfHealthyBudget - - # The default resources for a pod. - resources: - limits: - cpu: "1" - memory: 1Gi - requests: - cpu: 500m - memory: 1Gi - - ## Optionally specify extra environment variables to add to papyrus container - env: [] - # - name: FOO - # value: BAR - - extraArgs: {} # Optional additional deployment args - # foo: "bar" - -# Service variables for a papyrus pod. -service: - # Specify service type, supported options are ClusterIP, LoadBalancer - type: ClusterIP - ports: - rpc: - enabled: true - port: 8080 - protocol: TCP - monitoring: - enabled: true - port: 8081 - protocol: TCP - -p2pService: - enabled: true - # supported options are ClusterIP, LoadBalancer - type: ClusterIP - port: 10000 - protocol: TCP - # If service type is ClusterIP, - # Set static ip for the kubernetes service. Note that the ip address is not reserved and there might be a chance for ip collision. - # See https://kubernetes.io/docs/concepts/services-networking/cluster-ip-allocation/ to understand how to avoid such case. - clusterIP: - # If service type is LoadBalancer, - # Set static ip for the loadbalancer. Note that a static IP needs to be reserved first in a cloud environment in order to use it. - loadBalancerIP: - annotations: {} - -# Persistent volume claim variables for a papyrus pod. -pvc: - # Recommended size is at least 512Gi. - size: 512Gi - # Is is recommended to use an SSD volume (such as GKE premium-rwo). - storageClass: "" - # Use an existing snapshot for the node's data. The kubernetes volumesnapshot object should - # exist in the same namespace as the rest of the resources. - restoreFromSnapshot: - enabled: false - snapshotName: my-snapshot - -# Configure Ingress. -ingress: - # Should an ingress object be created - enabled: false - # Ingress class type. - type: - # Ingress object name in Kubernetes. - name: - # Host name to create Ingress rules. - host: - # Ingress path type. - pathType: - # Annotations to apply to the node ingress. - annotations: {} - -# GrafanaDashboad CRD configuration -# This is relevant for Grafana Operator users https://grafana.github.io/grafana-operator/docs/ -grafanaDashboard: - # Should the GrafanaDashboard object be installed - enabled: false - -grafanaAlerts: - enabled: false - -# Backup mode -backup: - enabled: false - sleepInterval: 6h - compress: false - aws: - s3BucketName: my-backup-bucket-name - s3BucketRegion: my-backup-bucket-region - accessKeyId: my aws_access_key_id - secretAccessKey: my aws_secret_access_key diff --git a/deployments/papyrus/install_papyrus.py b/deployments/papyrus/install_papyrus.py deleted file mode 100644 index 13a8a1a7240..00000000000 --- a/deployments/papyrus/install_papyrus.py +++ /dev/null @@ -1,121 +0,0 @@ -import argparse -import subprocess -import sys - -GRAFANA_DASHBOARD_TEMPLATE_FILE_PATH = "monitoring/templates/grafana_dashboard.json" -GRAFANA_ALERTS_TEMPLATE_FILE_PATH = "monitoring/templates/grafana_alerts.json" -GRAFANA_DASHBOARD_DESTINATION_FILE_PATH = "helm/Monitoring/grafana_dashboard.json" -GRAFANA_ALERTS_DESTINATION_FILE_PATH = "helm/Monitoring/grafana_alerts.json" - - -# TODO(AlonDo): Add function to deploy monitoring dashboard. -def parse_command_line_args(): - parser = argparse.ArgumentParser(description="Install Papyrus node.") - parser.add_argument( - "--release_name", type=str, required=True, help="Name for the helm release." - ) - parser.add_argument( - "--namespace", type=str, required=True, help="Target namespace for the Papyrus node." - ) - parser.add_argument( - "--create_namespace", - action="store_true", - default=False, - help="Enabling this option will install a new namespace with the given name.", - ) - parser.add_argument( - "--values_file", action="store", default=None, help="Add additional values file." - ) - parser.add_argument( - "--with_alerts", - action="store_true", - default=False, - help="Enabling this option will also deploy a grafana alerts deashboard with the pod.", - ) - parser.add_argument( - "--prometheus_uid", - type=str, - required=False, - help="UID for prometheus (to use with Grafana).", - ) - parser.add_argument( - "--old_version", - type=str, - required=False, - help="Represents previous RPC version for the desired env (e.g. v0_3).", - ) - parser.add_argument( - "--new_version", - type=str, - required=False, - help="Represents current RPC version for the desired env (e.g. v0_4).", - ) - parser.add_argument( - "--dry_run", - action="store_true", - default=False, - help="Enabling this option will dry run the helm upgrade.", - ) - parser.add_argument( - "--helm_deployment_dir", - type=str, - required=False, - default="./deployments/helm/", - help="Relative path to the helm deployment directory (default is ./deployments/helm/.", - ) - - return parser.parse_args() - - -def generate_grafana_tokens( - grafana_namespace: str, prometheus_uid: str, template_path: str, destination_path: str -): - grafana_template_lines = open(template_path).readlines() - grafana_dashboard_lines = list() - for line in grafana_template_lines: - grafana_dashboard_lines.append( - line.replace("NAMESPACE", grafana_namespace).replace("${DS_PROMETHEUS}", prometheus_uid) - ) - grafana_dashboard = "".join(line for line in grafana_dashboard_lines) - grafana_deployment_file = open(destination_path, "a") - # Delete previous file contents. - grafana_deployment_file.truncate(0) - grafana_deployment_file.write(grafana_dashboard) - grafana_deployment_file.flush() - - -def main(): - args = parse_command_line_args() - print(args) - # The CMD assumes this script is being run from the root directory. - cmd = f"helm upgrade --install {args.release_name} {args.helm_deployment_dir} --namespace {args.namespace}" - if args.create_namespace: - cmd += " --create-namespace" - if args.values_file: - cmd += f" -f {args.values_file}" - if args.with_alerts: - assert ( - args.prometheus_uid is not None - ), "Must provide Prometheus UID when deploying with Grafana." - generate_grafana_tokens( - grafana_namespace=args.namespace, - prometheus_uid=args.prometheus_uid, - template_path=GRAFANA_DASHBOARD_TEMPLATE_FILE_PATH, - destination_path=GRAFANA_DASHBOARD_DESTINATION_FILE_PATH, - ) - generate_grafana_tokens( - grafana_namespace=args.namespace, - prometheus_uid=args.prometheus_uid, - template_path=GRAFANA_ALERTS_TEMPLATE_FILE_PATH, - destination_path=GRAFANA_ALERTS_DESTINATION_FILE_PATH, - ) - - if args.dry_run: - cmd += " --dry-run" - - print(f"running {cmd}...") - subprocess.Popen(cmd, shell=True) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/deployments/papyrus/monitoring/templates/grafana_alerts.json b/deployments/papyrus/monitoring/templates/grafana_alerts.json deleted file mode 100644 index 5c45b36d7b6..00000000000 --- a/deployments/papyrus/monitoring/templates/grafana_alerts.json +++ /dev/null @@ -1,7768 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "Prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": [], - "__requires": [ - { - "type": "panel", - "id": "alertlist", - "name": "Alert list", - "version": "" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "8.5.5" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph (old)", - "version": "" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": null, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 64, - "panels": [], - "title": "Main Alerts", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "gridPos": { - "h": 27, - "w": 6, - "x": 0, - "y": 1 - }, - "id": 4, - "options": { - "alertName": "(Papyrus)", - "dashboardAlerts": false, - "dashboardTitle": "", - "maxItems": 90, - "showOptions": "current", - "sortOrder": 3, - "stateFilter": { - "alerting": false, - "execution_error": false, - "no_data": false, - "ok": false, - "paused": false, - "pending": false - }, - "tags": [] - }, - "pluginVersion": "8.5.5", - "title": "Alerts Panel", - "type": "alertlist" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 2 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "10m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "min" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "10m", - "frequency": "1m", - "handler": 1, - "name": "Unsynced headers alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 6, - "x": 6, - "y": 1 - }, - "hiddenSeries": false, - "id": 67, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"NAMESPACE\"} - papyrus_header_marker{kubernetes_namespace=\"NAMESPACE\"}", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "gt", - "value": 2, - "visible": true - } - ], - "timeRegions": [], - "title": "Unsynced headers", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 2 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "10m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "min" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "10m", - "frequency": "1m", - "handler": 1, - "name": "Unsynced bodies alert", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 6, - "x": 12, - "y": 1 - }, - "hiddenSeries": false, - "id": 68, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"NAMESPACE\"} - papyrus_body_marker{kubernetes_namespace=\"NAMESPACE\"}", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "gt", - "value": 2, - "visible": true - } - ], - "timeRegions": [], - "title": "Unsynced bodies", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 2 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "10m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "min" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "10m", - "frequency": "1m", - "handler": 1, - "name": "Unsynced state diffs alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 1 - }, - "hiddenSeries": false, - "id": 69, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"NAMESPACE\"} - papyrus_state_marker{kubernetes_namespace=\"NAMESPACE\"}", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "gt", - "value": 2, - "visible": true - } - ], - "timeRegions": [], - "title": "Unsynced state diffs", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 2 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "10m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "min" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "10m", - "frequency": "1m", - "handler": 1, - "name": "Unsynced compiled classes alert", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 6, - "x": 6, - "y": 10 - }, - "hiddenSeries": false, - "id": 70, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"NAMESPACE\"} - papyrus_compiled_class_marker{kubernetes_namespace=\"NAMESPACE\"}", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "gt", - "value": 2, - "visible": true - } - ], - "timeRegions": [], - "title": "Unsynced compiled classes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 100 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Execution Request Load Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Rate of requests for methods that change Starknet", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 6, - "x": 12, - "y": 10 - }, - "id": 10, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rate(sum(rpc_incoming_requests{kubernetes_namespace=\"NAMESPACE\", method=~\"addDeclareTransaction|addDeployAccountTransaction|addInvokeTransaction\"})[10m:])", - "legendFormat": "Incoming requests rate (in 10 mins)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 100, - "visible": true - } - ], - "title": "Requests Rate - Execute Functions", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 100 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Call Request Load Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Rate of requests for methods that query Starknet", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 10 - }, - "id": 6, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rate(sum(rpc_incoming_requests{kubernetes_namespace=\"NAMESPACE\", method!~\"addDeclareTransaction|addDeployAccountTransaction|addInvokeTransaction\"})[10m:])", - "legendFormat": "Incoming requests rate (in 10 mins)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 100, - "visible": true - } - ], - "title": "Requests Rate - Call Functions", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 0.00001 - ], - "type": "lt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "No New Block Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 6, - "x": 6, - "y": 19 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.5.5", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "max(rate(papyrus_central_block_marker{kubernetes_namespace=\"NAMESPACE\"}[4m])*225)", - "legendFormat": "Number of Blocks", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "lt", - "value": 0.00001, - "visible": true - } - ], - "timeRegions": [], - "title": "Starknet Block Creation Rate", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 28 - }, - "id": 52, - "panels": [], - "title": "Latency Alert - Execute Functions", - "type": "row" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet addDeclareTransaction OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 29 - }, - "id": 55, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"addDeclareTransaction\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "addDeclareTransaction OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet addDeclareTransaction NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 29 - }, - "id": 54, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"addDeclareTransaction\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "addDeclareTransaction NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet addDeployAccountTransaction OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 29 - }, - "id": 57, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"addDeployAccountTransaction\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "addDeployAccountTransaction OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet addDeployAccountTransaction NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 29 - }, - "id": 56, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"addDeployAccountTransaction\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "addDeployAccountTransaction NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet addInvokeTransaction OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 36 - }, - "id": 53, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"addInvokeTransaction\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "addInvokeTransaction OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet addInvokeTransaction NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 36 - }, - "id": 58, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"addInvokeTransaction\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "addInvokeTransaction NEW_VERSION Latency", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 43 - }, - "id": 60, - "panels": [], - "title": "Latency Alert - Call Functions - Blocks", - "type": "row" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet blockHashAndNumber OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 44 - }, - "id": 13, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"blockHashAndNumber\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "blockHashAndNumber OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet blockHashAndNumber NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 44 - }, - "id": 14, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"blockHashAndNumber\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "blockHashAndNumber NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet blockNumber OLD_VERSION Latency Alert(Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 44 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"blockNumber\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "blockNumber OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet blockNumber NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 44 - }, - "id": 9, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"blockNumber\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "blockNumber NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getBlockTransactionCount OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 51 - }, - "id": 18, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getBlockTransactionCount\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getBlockTransactionCount OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getBlockTransactionCount NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 51 - }, - "id": 22, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getBlockTransactionCount\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getBlockTransactionCount NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getBlockWithTxHashes OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 51 - }, - "id": 25, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getBlockWithTxHashes\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getBlockWithTxHashes OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getBlockWithTxHashes NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 51 - }, - "id": 24, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getBlockWithTxHashes\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getBlockWithTxHashes NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getBlockWithTxs OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 58 - }, - "id": 28, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getBlockWithTxs\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getBlockWithTxs OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getBlockWithTxs NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 58 - }, - "id": 26, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getBlockWithTxs\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getBlockWithTxs NEW_VERSION Latency", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 65 - }, - "id": 62, - "panels": [], - "title": "Latency Alert - Call Functions - Transactions", - "type": "row" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getTransactionByBlockIdAndIndex OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 66 - }, - "id": 43, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getTransactionByBlockIdAndIndex\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getTransactionByBlockIdAndIndex OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getTransactionByBlockIdAndIndex NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 66 - }, - "id": 42, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getTransactionByBlockIdAndIndex\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getTransactionByBlockIdAndIndex NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getTransactionByHash OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 66 - }, - "id": 45, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getTransactionByHash\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getTransactionByHash OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getTransactionByHash NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 66 - }, - "id": 44, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getTransactionByHash\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getTransactionByHash NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getTransactionReceipt OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 73 - }, - "id": 47, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getTransactionReceipt\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getTransactionReceipt OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getTransactionReceipt NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 73 - }, - "id": 46, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getTransactionReceipt\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getTransactionReceipt NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet pendingTransactions OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 73 - }, - "id": 49, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"pendingTransactions\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "pendingTransactions OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet pendingTransactions NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 73 - }, - "id": 48, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"pendingTransactions\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "pendingTransactions NEW_VERSION Latency", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 80 - }, - "id": 66, - "panels": [], - "title": "Latency Alert - Call Functions - Classes", - "type": "row" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getClass OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 81 - }, - "id": 30, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getClass\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getClass OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getClass NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 81 - }, - "id": 27, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getClass\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getClass NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getClassAt OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 81 - }, - "id": 31, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getClassAt\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getClassAt OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getClassAt NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 81 - }, - "id": 29, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getClassAt\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getClassAt NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getClassHashAt OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 88 - }, - "id": 23, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getClassHashAt\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getClassHashAt OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getClassHashAt NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 88 - }, - "id": 32, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getClassHashAt\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getClassHashAt NEW_VERSION Latency", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 95 - }, - "id": 12, - "panels": [], - "title": "Latency Alert - Call Functions - Others", - "type": "row" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet call OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 96 - }, - "id": 15, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"call\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "call OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet call NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 96 - }, - "id": 16, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"call\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "call NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet chainId OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 96 - }, - "id": 19, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"chainId\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "chainId OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet chainId NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 96 - }, - "id": 17, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"chainId\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "chainId NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet estimateFee OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 103 - }, - "id": 21, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"estimateFee\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "estimateFee OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet estimateFee NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 103 - }, - "id": 20, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"estimateFee\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "estimateFee NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getNonce OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 103 - }, - "id": 37, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getNonce\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getNonce OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getNonce NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 103 - }, - "id": 36, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getNonce\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getNonce NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getEvents OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 110 - }, - "id": 35, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getEvents\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getEvents OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getEvents NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 110 - }, - "id": 34, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getEvents\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getEvents NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getStorageAt OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 110 - }, - "id": 41, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getStorageAt\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getStorageAt OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getStorageAt NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 110 - }, - "id": 40, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getStorageAt\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getStorageAt NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getStateUpdate OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 117 - }, - "id": 39, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getStateUpdate\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getStateUpdate OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet getStateUpdate NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 117 - }, - "id": 38, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"getStateUpdate\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "getStateUpdate NEW_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet syncing OLD_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 117 - }, - "id": 33, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"syncing\",version=\"OLD_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "syncing OLD_VERSION Latency", - "type": "timeseries" - }, - { - "alert": { - "alertRuleTags": {}, - "conditions": [ - { - "evaluator": { - "params": [ - 1 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "last" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "for": "5m", - "frequency": "1m", - "handler": 1, - "name": "Starknet syncing NEW_VERSION Latency Alert (Papyrus)", - "noDataState": "no_data", - "notifications": [] - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Latency (seconds)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 117 - }, - "id": 50, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"NAMESPACE\", method=\"syncing\",version=\"NEW_VERSION\",quantile=\"0.95\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "op": "gt", - "value": 1, - "visible": true - } - ], - "title": "syncing NEW_VERSION Latency", - "type": "timeseries" - } - ], - "refresh": false, - "schemaVersion": 36, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Papyrus-alerts", - "uid": null, - "version": 58, - "weekStart": "" -} diff --git a/deployments/papyrus/monitoring/templates/grafana_dashboard.json b/deployments/papyrus/monitoring/templates/grafana_dashboard.json deleted file mode 100644 index 3cfdc1d2da0..00000000000 --- a/deployments/papyrus/monitoring/templates/grafana_dashboard.json +++ /dev/null @@ -1,9286 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "Prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": [], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "8.5.5" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": null, - "iteration": 1697099323886, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 30, - "panels": [], - "title": "Sync", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The difference, in seconds, between the time the node received the batch and the batch creation timestamp.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisGridShow": true, - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 100, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 4, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 24, - "x": 0, - "y": 1 - }, - "id": 20, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "papyrus_header_latency{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "legendFormat": "Latency (seconds)", - "range": true, - "refId": "A" - } - ], - "title": "Header Latency", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The Current Starknet block number.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "max": 0, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "blue", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 8 - }, - "id": 16, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "format": "table", - "hide": false, - "instant": true, - "range": false, - "refId": "A" - } - ], - "title": "Starknet Block Number", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The block number of the last Starknet block that was finalized on L1.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "max": 0, - "noValue": "0", - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "blue", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 15, - "x": 0, - "y": 14 - }, - "id": 15, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "papyrus_base_layer_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "format": "table", - "hide": false, - "instant": true, - "range": false, - "refId": "A" - } - ], - "title": "L1 Block Number", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The difference between Starknet Block Number and L1 Block Number.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - }, - { - "color": "light-blue", - "value": 100 - }, - { - "color": "super-light-orange", - "value": 1000 - }, - { - "color": "orange", - "value": 10000 - }, - { - "color": "red", - "value": 100000 - } - ] - }, - "unit": "locale" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 9, - "x": 15, - "y": 14 - }, - "id": 69, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} - papyrus_base_layer_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "L1 Block Number Diff", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The current block header marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "max": 0, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 19 - }, - "id": 21, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "papyrus_header_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "format": "table", - "hide": false, - "instant": true, - "range": false, - "refId": "A" - } - ], - "title": "Block Header", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The current block body marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "max": 0, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 19 - }, - "id": 18, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "papyrus_body_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "format": "table", - "hide": false, - "instant": true, - "range": false, - "refId": "A" - } - ], - "title": "Block Body", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The current state update marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "max": 0, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 19 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "papyrus_state_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "format": "table", - "hide": false, - "instant": true, - "range": false, - "refId": "A" - } - ], - "title": "State Update", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The current compiled class marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "max": 0, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 19 - }, - "id": 10, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "papyrus_compiled_class_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "format": "table", - "hide": false, - "instant": true, - "range": false, - "refId": "A" - } - ], - "title": "Compiled Class (CASM)", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The difference between the current Starknet block number and the current block header marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "super-light-green", - "value": 1 - }, - { - "color": "super-light-orange", - "value": 5 - }, - { - "color": "orange", - "value": 10 - }, - { - "color": "red", - "value": 20 - } - ] - }, - "unit": "locale" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 24 - }, - "id": 11, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} - papyrus_header_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "Block Header Diff", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The difference between the current Starknet block number and the current block body marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "super-light-green", - "value": 1 - }, - { - "color": "super-light-orange", - "value": 5 - }, - { - "color": "orange", - "value": 10 - }, - { - "color": "red", - "value": 20 - } - ] - }, - "unit": "locale" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 24 - }, - "id": 66, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} - papyrus_body_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "Block Body Diff", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The difference between the current Starknet block number and the current state update marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "super-light-green", - "value": 1 - }, - { - "color": "super-light-orange", - "value": 5 - }, - { - "color": "orange", - "value": 10 - }, - { - "color": "red", - "value": 20 - } - ] - }, - "unit": "locale" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 24 - }, - "id": 67, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} - papyrus_state_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "State Update Diff", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The difference between the current Starknet block number and the current compiled class marker.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "super-light-green", - "value": 1 - }, - { - "color": "super-light-orange", - "value": 5 - }, - { - "color": "orange", - "value": 10 - }, - { - "color": "red", - "value": 20 - } - ] - }, - "unit": "locale" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 24 - }, - "id": 68, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} - papyrus_compiled_class_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"}", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "Compiled Class (CASM) Diff", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The percentage of the current block header marker from the current Starknet block number.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "orange", - "value": 0.5 - }, - { - "color": "super-light-orange", - "value": 0.75 - }, - { - "color": "super-light-green", - "value": 0.9 - }, - { - "color": "green", - "value": 1 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 29 - }, - "id": 65, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "floor(papyrus_header_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} / papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} * 10^4) / 10^4", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "Block Header %", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The percentage of the current block body marker from the current Starknet block number.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "orange", - "value": 0.5 - }, - { - "color": "super-light-orange", - "value": 0.75 - }, - { - "color": "super-light-green", - "value": 0.9 - }, - { - "color": "green", - "value": 1 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 29 - }, - "id": 17, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "floor(papyrus_body_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} / papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} * 10^4) / 10^4", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "Block Body %", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The percentage of the current state update marker from the current Starknet block number.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "orange", - "value": 0.5 - }, - { - "color": "super-light-orange", - "value": 0.75 - }, - { - "color": "super-light-green", - "value": 0.9 - }, - { - "color": "green", - "value": 1 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 29 - }, - "id": 9, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "floor(papyrus_state_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} / papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} * 10^4) / 10^4", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "State Update %", - "transformations": [], - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The percentage of the current compiled class marker from the current Starknet block number.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "orange", - "value": 0.5 - }, - { - "color": "super-light-orange", - "value": 0.75 - }, - { - "color": "super-light-green", - "value": 0.9 - }, - { - "color": "green", - "value": 1 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Central" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 29 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "floor(papyrus_compiled_class_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} / papyrus_central_block_marker{kubernetes_namespace=\"$namespace\", pod=~\"$pod\"} * 10^4) / 10^4", - "hide": false, - "refId": "Head / Central" - } - ], - "title": "Compiled Class (CASM) %", - "transformations": [], - "type": "stat" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 34 - }, - "id": 34, - "panels": [], - "title": "Resources usage", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "CPU usage percentage.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Percentage", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 100, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percent" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Value" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "orange", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 35 - }, - "id": 36, - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "rate(container_cpu_usage_seconds_total{namespace=\"$namespace\", pod=~\"$pod\", container!=\"\"}[1m])", - "interval": "", - "legendFormat": "{{container}}", - "range": true, - "refId": "A" - } - ], - "title": "CPU usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Memory usage in bytes.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bytes", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 100, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 4, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "bytes" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Value" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 12, - "y": 35 - }, - "id": 38, - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\", pod=~\"$pod\", container!=\"\"}) by (container, pod)", - "legendFormat": "{{container}}", - "range": true, - "refId": "A" - } - ], - "title": "Memory usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Storage usage in bytes.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bytes", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 100, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 3, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Value" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 45 - }, - "id": 40, - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "kubelet_volume_stats_capacity_bytes{namespace=\"$namespace\"} - kubelet_volume_stats_available_bytes{namespace=\"$namespace\"}", - "legendFormat": "{{namespace}}", - "range": true, - "refId": "A" - } - ], - "title": "Storage usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Networking usage.\nTx - Transmitting.\nRx - Receiving.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bytes / Minute", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 100, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "Bps" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Tx" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Rx" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 45 - }, - "id": 44, - "options": { - "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "table", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "- sum(rate(container_network_transmit_bytes_total{image!=\"\", namespace=\"$namespace\", pod=~\"$pod\"}[1m])) by (container, pod)", - "hide": false, - "legendFormat": "Tx", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(container_network_receive_bytes_total{image!=\"\", namespace=\"$namespace\", pod=~\"$pod\"}[1m])) by (container, pod)", - "hide": false, - "legendFormat": "Rx", - "range": true, - "refId": "B" - } - ], - "title": "Networking usage", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 53 - }, - "id": 32, - "panels": [], - "title": "Requests and latencies", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 54 - }, - "id": 62, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockNumber\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "blockNumber Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 54 - }, - "id": 70, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockNumber\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "blockNumber Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 54 - }, - "id": 71, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockNumber\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "blockNumber Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 54 - }, - "id": 72, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockNumber\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "blockNumber Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 61 - }, - "id": 73, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getNonce\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getNonce Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 61 - }, - "id": 74, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getNonce\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getNonce Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 61 - }, - "id": 75, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getNonce\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getNonce Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 61 - }, - "id": 76, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getNonce\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getNonce Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 68 - }, - "id": 84, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStateUpdate\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getStateUpdate Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 68 - }, - "id": 83, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStateUpdate\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getStateUpdate Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 68 - }, - "id": 82, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStateUpdate\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getStateUpdate Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 68 - }, - "id": 80, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStateUpdate\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getStateUpdate Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 75 - }, - "id": 77, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByHash\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getTransactionByHash Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 75 - }, - "id": 101, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByHash\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getTransactionByHash Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 75 - }, - "id": 79, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByHash\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getTransactionByHash Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 75 - }, - "id": 85, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByHash\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getTransactionByHash Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 82 - }, - "id": 103, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClass\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getClass Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 82 - }, - "id": 104, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClass\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getClass Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 82 - }, - "id": 105, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClass\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getClass Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 82 - }, - "id": 81, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClass\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getClass Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 89 - }, - "id": 106, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getEvents\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getEvents Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 89 - }, - "id": 107, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getEvents\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getEvents Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 89 - }, - "id": 108, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getEvents\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getEvents Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 89 - }, - "id": 86, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getNonce\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getEvents Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 96 - }, - "id": 109, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassAt\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getClassAt Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 96 - }, - "id": 110, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassAt\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getClassAt Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 96 - }, - "id": 102, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassAt\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getClassAt Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 96 - }, - "id": 87, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassAt\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getClassAt Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 103 - }, - "id": 112, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassHashAt\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getClassHashAt Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 103 - }, - "id": 113, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassHashAt\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getClassHashAt Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 103 - }, - "id": 114, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassHashAt\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getClassHashAt Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 103 - }, - "id": 88, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getClassHashAt\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getClassHashAt Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 110 - }, - "id": 115, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxs\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getBlockWithTxs Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 110 - }, - "id": 116, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxs\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getBlockWithTxs Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 110 - }, - "id": 117, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxs\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getBlockWithTxs Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 110 - }, - "id": 89, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxs\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getBlockWithTxs Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 117 - }, - "id": 118, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStorageAt\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getStorageAt Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 117 - }, - "id": 78, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStorageAt\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getStorageAt Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 117 - }, - "id": 111, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStorageAt\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getStorageAt Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 117 - }, - "id": 90, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getStorageAt\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getStorageAt Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 124 - }, - "id": 121, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockTransactionCount\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getBlockTransactionCount Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 124 - }, - "id": 122, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockTransactionCount\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getBlockTransactionCount Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 124 - }, - "id": 123, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockTransactionCount\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getBlockTransactionCount Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 124 - }, - "id": 91, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockTransactionCount\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getBlockTransactionCount Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 131 - }, - "id": 124, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionReceipt\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getTransactionReceipt Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 131 - }, - "id": 125, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionReceipt\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getTransactionReceipt Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 131 - }, - "id": 126, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionReceipt\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getTransactionReceipt Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 131 - }, - "id": 92, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionReceipt\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getTransactionReceipt Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 138 - }, - "id": 127, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxHashes\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getBlockWithTxHashes Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 138 - }, - "id": 128, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxHashes\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getBlockWithTxHashes Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 138 - }, - "id": 129, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxHashes\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getBlockWithTxHashes Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 138 - }, - "id": 93, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getBlockWithTxHashes\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getBlockWithTxHashes Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 145 - }, - "id": 130, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByBlockIdAndIndex\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "getTransactionByBlockIdAndIndex Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 145 - }, - "id": 131, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByBlockIdAndIndex\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "getTransactionByBlockIdAndIndex Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 145 - }, - "id": 132, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByBlockIdAndIndex\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "getTransactionByBlockIdAndIndex Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 145 - }, - "id": 94, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"getTransactionByBlockIdAndIndex\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "getTransactionByBlockIdAndIndex Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 152 - }, - "id": 133, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "addInvokeTransaction Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 152 - }, - "id": 134, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "addInvokeTransaction Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 152 - }, - "id": 135, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "addInvokeTransaction Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 152 - }, - "id": 95, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "addInvokeTransaction Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 159 - }, - "id": 136, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockHashAndNumber\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "blockHashAndNumber Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 159 - }, - "id": 137, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockHashAndNumber\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "blockHashAndNumber Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 159 - }, - "id": 138, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockHashAndNumber\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "blockHashAndNumber Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 159 - }, - "id": 96, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"blockHashAndNumber\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "blockHashAndNumber Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 166 - }, - "id": 139, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"chainId\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "chainId Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 166 - }, - "id": 140, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"chainId\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "chainId Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 166 - }, - "id": 141, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"chainId\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "chainId Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 166 - }, - "id": 97, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"chainId\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "chainId Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 173 - }, - "id": 142, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"syncing\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "syncing Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 173 - }, - "id": 143, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"syncing\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "syncing Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 173 - }, - "id": 144, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"syncing\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "syncing Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 173 - }, - "id": 98, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"syncing\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "syncing Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 180 - }, - "id": 100, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"call\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "call Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 180 - }, - "id": 119, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"call\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "call Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 180 - }, - "id": 120, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"call\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "call Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 180 - }, - "id": 99, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"call\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "call Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 169 - }, - "id": 153, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"simulateTransactions\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "simulateTransactions Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 169 - }, - "id": 154, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"simulateTransactions\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "simulateTransactions Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 169 - }, - "id": 155, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"simulateTransactions\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "simulateTransactions Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 169 - }, - "id": 156, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"simulateTransactions\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "simulateTransactions Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 176 - }, - "id": 157, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"estimateFee\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "estimateFee Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 176 - }, - "id": 158, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"estimateFee\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "estimateFee Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 176 - }, - "id": 159, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"estimateFee\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "estimateFee Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 176 - }, - "id": 160, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"estimateFee\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "estimateFee Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 183 - }, - "id": 145, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceTransaction\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "traceTransaction Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 183 - }, - "id": 146, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "traceTransaction Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 183 - }, - "id": 147, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceTransaction\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "traceTransaction Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 183 - }, - "id": 148, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "traceTransaction Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 190 - }, - "id": 149, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceBlockTransactions\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "traceBlockTransactions Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 190 - }, - "id": 150, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceBlockTransactions\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "traceBlockTransactions Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 190 - }, - "id": 151, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceBlockTransactions\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "traceBlockTransactions Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 190 - }, - "id": 152, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"traceBlockTransactions\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "traceBlockTransactions Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 197 - }, - "id": 164, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "addInvokeTransaction Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 197 - }, - "id": 163, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "addInvokeTransaction Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 197 - }, - "id": 162, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "addInvokeTransaction Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 197 - }, - "id": 161, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addInvokeTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "addInvokeTransaction Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 204 - }, - "id": 172, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeployAccountTransaction\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "addDeployAccountTransaction Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 204 - }, - "id": 170, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeployAccountTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "addDeployAccountTransaction Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 204 - }, - "id": 167, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeployAccountTransaction\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "addDeployAccountTransaction Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 204 - }, - "id": 166, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeployAccountTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "addDeployAccountTransaction Latencies", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of incoming requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 211 - }, - "id": 171, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_incoming_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeclareTransaction\",version=\"$version\"}", - "legendFormat": "Incoming requests", - "range": true, - "refId": "A" - } - ], - "title": "addDeclareTransaction Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Number of failed requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 211 - }, - "id": 169, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_failed_requests{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeclareTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "Failed requests", - "range": true, - "refId": "C" - } - ], - "title": "addDeclareTransaction Failed Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latency of the quickest 95% of the requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 8, - "y": 211 - }, - "id": 168, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto" - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeclareTransaction\",version=\"$version\",quantile=\"0.95\"}", - "hide": false, - "legendFormat": "Latency", - "range": true, - "refId": "B" - } - ], - "title": "addDeclareTransaction Latency (0.95)", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The maximal latencies of different percentages of the quickest requests.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 10, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "locale" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 211 - }, - "id": 165, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rpc_request_latency_seconds{kubernetes_namespace=\"$namespace\", pod=~\"$pod\", method=\"addDeclareTransaction\",version=\"$version\"}", - "hide": false, - "legendFormat": "{{quantile}}", - "range": true, - "refId": "B" - } - ], - "title": "addDeclareTransaction Latencies", - "type": "timeseries" - } - ], - "refresh": false, - "schemaVersion": 36, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(papyrus_header_marker, kubernetes_namespace)", - "hide": 0, - "includeAll": false, - "multi": false, - "name": "namespace", - "options": [], - "query": { - "query": "label_values(papyrus_header_marker, kubernetes_namespace)", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(papyrus_header_marker{kubernetes_namespace=\"$namespace\"}, pod)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "pod", - "options": [], - "query": { - "query": "label_values(papyrus_header_marker{kubernetes_namespace=\"$namespace\"}, pod)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": true, - "text": "V0_5", - "value": "V0_5" - }, - "hide": 0, - "includeAll": false, - "multi": false, - "name": "version", - "options": [ - { - "selected": false, - "text": "V0_4", - "value": "V0_4" - }, - { - "selected": false, - "text": "V0_5", - "value": "V0_5" - } - ], - "query": "V0_4,V0_5", - "queryValue": "None", - "skipUrlSync": false, - "type": "custom" - } - ] - }, - "time": { - "from": "now-30m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Papyrus-node-data", - "uid": null, - "version": 60, - "weekStart": "" -} \ No newline at end of file diff --git a/docs/papyrus/CONTRIBUTING.md b/docs/papyrus/CONTRIBUTING.md index afcc62da74c..0f5a6f6b737 100644 --- a/docs/papyrus/CONTRIBUTING.md +++ b/docs/papyrus/CONTRIBUTING.md @@ -44,9 +44,6 @@ git clone https://github.com/starkware-libs/papyrus Then, you will need to install - [Rust](https://www.rust-lang.org/tools/install) (1.73 or higher) - [Rust nightly toolchain 2022-07-27](https://rust-lang.github.io/rustup/installation/index.html#installing-nightly) -- [Ganache 7.4.3](https://www.npmjs.com/package/ganache) - - You'll need to install 7.4.3 and not a version above it. We'll relax this in the future. - - You'll need Ganache only for the tests of the [papyrus_base_layer](../../crates/papyrus_base_layer/) crate. ### CI Your code will need to pass [CI](../.github/workflows/ci.yml) before it can be merged. This means your code will need to: diff --git a/scripts/check_test_trigger.py b/scripts/check_test_trigger.py index 4239c07ed2a..0f3fe287e24 100644 --- a/scripts/check_test_trigger.py +++ b/scripts/check_test_trigger.py @@ -9,16 +9,20 @@ from tests_utils import get_local_changes, get_tested_packages -def is_file_triggered(commit_id: str, trigger_patterns: List[str]) -> bool: +def is_file_triggered( + commit_id: str, trigger_patterns: List[str], exclude_patterns: List[str] +) -> bool: """ Returns True if any file changed since `commit_id` matches any of the given - wildcard patterns in `trigger_patterns`. + wildcard patterns in `trigger_patterns` and does not match any in `exclude_patterns`. """ changed_files = get_local_changes(".", commit_id) for changed in changed_files: normalized = changed.replace(os.sep, "/") for pattern in trigger_patterns: - if fnmatch.fnmatch(normalized, pattern): + if fnmatch.fnmatch(normalized, pattern) and not any( + fnmatch.fnmatch(normalized, exclude_pattern) for exclude_pattern in exclude_patterns + ): return True return False @@ -48,6 +52,12 @@ def parse_args() -> argparse.Namespace: default="", help="Comma-separated list of file/path patterns that should trigger the test.", ) + parser.add_argument( + "--path_triggers_exclude", + type=str, + default="", + help="Comma-separated list of file/path patterns that should not trigger the test. Takes precedence over path_triggers.", + ) return parser.parse_args() @@ -56,6 +66,7 @@ def main(): crate_triggers: Set[str] = set(filter(None, args.crate_triggers.split(","))) path_triggers: List[str] = list(filter(None, args.path_triggers.split(","))) + path_triggers_exclude: List[str] = list(filter(None, args.path_triggers_exclude.split(","))) tested = get_tested_packages( changes_only=True, commit_id=args.commit_id, include_dependencies=True @@ -67,7 +78,7 @@ def main(): crate_trigger = any(crate in crate_triggers for crate in tested) print(f"crate_trigger: {crate_trigger}", file=sys.stderr) - file_trigger = is_file_triggered(args.commit_id, path_triggers) + file_trigger = is_file_triggered(args.commit_id, path_triggers, path_triggers_exclude) print(f"file_trigger: {file_trigger}", file=sys.stderr) should_run = crate_trigger or file_trigger diff --git a/scripts/committer/generate_committer_flamegraph.sh b/scripts/committer/generate_committer_flamegraph.sh old mode 100644 new mode 100755 index 6eee9ead28f..0f558a70a6e --- a/scripts/committer/generate_committer_flamegraph.sh +++ b/scripts/committer/generate_committer_flamegraph.sh @@ -7,7 +7,10 @@ fi # Restore security level in perf_event_paranoid at the end. ORIGINAL_PARANOID=$(echo $(sysctl kernel.perf_event_paranoid) | grep -o '[0-9]$') -trap 'sudo sysctl kernel.perf_event_paranoid=$ORIGINAL_PARANOID' EXIT SIGINT SIGTERM + +# Create temporary file for committer input +TEMP_INPUT_FILE=$(mktemp) +trap 'sudo sysctl kernel.perf_event_paranoid=$ORIGINAL_PARANOID; rm -f "$TEMP_INPUT_FILE"' EXIT SIGINT SIGTERM if ! command -v jq; then cargo install jq @@ -24,4 +27,8 @@ BENCH_INPUT_FILES_PREFIX=$(cat ${ROOT_DIR}/crates/starknet_committer_and_os_cli/ # Lower security level in perf_event_paranoid to 2 to allow cargo to use perf without running on root. sudo sysctl kernel.perf_event_paranoid=2 -gcloud storage cat gs://committer-testing-artifacts/${BENCH_INPUT_FILES_PREFIX}/committer_flow_inputs.json | jq -r .committer_input | CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -p starknet_committer_and_os_cli -- commit +# Download and extract committer input to temporary file +gcloud storage cat gs://committer-testing-artifacts/${BENCH_INPUT_FILES_PREFIX}/committer_flow_inputs.json | jq -r .committer_input > "$TEMP_INPUT_FILE" + +# Run flamegraph with the temporary file as input +CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -p starknet_committer_and_os_cli -- committer commit --input-path "$TEMP_INPUT_FILE" diff --git a/scripts/local_presubmit.sh b/scripts/local_presubmit.sh index 9b6229dde76..5e7accb0908 100755 --- a/scripts/local_presubmit.sh +++ b/scripts/local_presubmit.sh @@ -70,17 +70,6 @@ setup_env_variables_from_yml() { } install_dependencies() { - packages=("@commitlint/cli" "@commitlint/config-conventional") - - for pkg in "${packages[@]}"; do - if npm list "$pkg" >/dev/null 2>&1; then - log_debug "$pkg is already installed ✅" - else - echo "$pkg is NOT installed ❌ — installing..." - npm install "$pkg" - fi - done - # List of crate names to check/install CRATES=("taplo-cli" "cargo-machete") @@ -195,29 +184,6 @@ restore_old_env() { fi } -add_commit_lint_to_path() { - # Step 1: Try to locate commitlint using which. - COMMITLINT_PATH="$(which commitlint)" - if [ -n "$COMMITLINT_PATH" ]; then - log_debug 'commitlint found in $PATH' - return - fi - - # Step 2: If which fails, use find to search from home directory - echo "commitlint not found via which. Consider adding it to your path. Searching with find..." - COMMITLINT_PATH="$(find ~/ \( -type f -o -type l \) -name commitlint -perm -u+x 2>/dev/null | grep "bin/" | head -n 1 | xargs dirname)" - - # Step 3: Add to path if needed - if [ -n "$COMMITLINT_PATH" ]; then - echo "commitlint found at: $COMMITLINT_PATH" - ORIGINAL_PATH="$PATH" - export PATH="$COMMITLINT_PATH:$PATH" - else - echo "commitlint not found in PATH or local directories." >&2 - exit 1 - fi -} - # Parse command-line arguments parse_args "$@" @@ -233,7 +199,6 @@ setup_new_venv install_yq setup_env_variables_from_yml install_dependencies -add_commit_lint_to_path # Change directory to the top of the repository which is needed for the presubmit script to run. cd "$REPO_LOCATION" || { diff --git a/scripts/presubmit_fast_checks.py b/scripts/presubmit_fast_checks.py index cacb9d7db57..22dc3610466 100644 --- a/scripts/presubmit_fast_checks.py +++ b/scripts/presubmit_fast_checks.py @@ -104,25 +104,6 @@ def __init__(self): super().__init__(commands=[["git", "submodule", "status"]]) -class CommitLintCheck(ExternalCommandCheck): - def __init__(self, from_commit_hash: str, to_commit_hash: str): - assert from_commit_hash, "from_commit_hash is required for commit lint check." - assert to_commit_hash, "to_commit_hash is required for commit lint check." - super().__init__( - commands=[["commitlint"] + ["--from", from_commit_hash] + ["--to", to_commit_hash]] - ) - - @classmethod - def required_args(cls: type[TCheck]) -> set[PresubmitArg]: - return {PresubmitArg.FROM_COMMIT_HASH, PresubmitArg.TO_COMMIT_HASH} - - @classmethod - def from_args(cls, args: argparse.Namespace): - return CommitLintCheck( - from_commit_hash=args.from_commit_hash, to_commit_hash=args.to_commit_hash - ) - - class TodosCheck(Check): def __init__(self, from_commit_hash: str): assert from_commit_hash, "from_commit_hash is required for TODOs check." @@ -224,7 +205,6 @@ def get_checks_to_run(args: argparse.Namespace, all_checks: dict[str, type[Check def main(): all_check_classes = [ - CommitLintCheck, GitSubmodulesCheck, TodosCheck, CargoLockCheck, diff --git a/scripts/prod/common_lib.py b/scripts/prod/common_lib.py new file mode 100644 index 00000000000..9a6ae613ccb --- /dev/null +++ b/scripts/prod/common_lib.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +import sys +from enum import Enum +from typing import Optional + + +class Colors(Enum): + """ANSI color codes for terminal output""" + + RED = "\033[1;31m" + GREEN = "\033[1;32m" + YELLOW = "\033[1;33m" + BLUE = "\033[1;34m" + RESET = "\033[0m" + + +def print_colored(message: str, color: Colors = Colors.RESET, file=sys.stdout) -> None: + """Print message with color""" + print(f"{color.value}{message}{Colors.RESET.value}", file=file) + + +def print_error(message: str) -> None: + print_colored(message, color=Colors.RED, file=sys.stderr) + + +class RestartStrategy(Enum): + """Strategy for restarting nodes.""" + + ALL_AT_ONCE = "all_at_once" + ONE_BY_ONE = "one_by_one" + NO_RESTART = "no_restart" + + +def restart_strategy_converter(strategy_name: str) -> RestartStrategy: + """Convert string to RestartStrategy enum with informative error message""" + RESTART_STRATEGY_PREFIX = f"{RestartStrategy.__name__}." + if strategy_name.startswith(RESTART_STRATEGY_PREFIX): + strategy_name = strategy_name[len(RESTART_STRATEGY_PREFIX) :] + + strategy_name = strategy_name.lower() + + try: + return RestartStrategy(strategy_name) + except KeyError: + valid_strategies = ", ".join([strategy.value for strategy in RestartStrategy]) + raise argparse.ArgumentTypeError( + f"Invalid restart strategy '{strategy_name}'. Valid options are: {valid_strategies}" + ) + + +class Service(Enum): + """Service types mapping to their configmap and pod names.""" + + Core = ("sequencer-core-config", "sequencer-core-statefulset-0") + Gateway = ("sequencer-gateway-config", "sequencer-gateway-deployment") + HttpServer = ( + "sequencer-httpserver-config", + "sequencer-httpserver-deployment", + ) + L1 = ("sequencer-l1-config", "sequencer-l1-deployment") + Mempool = ("sequencer-mempool-config", "sequencer-mempool-deployment") + SierraCompiler = ( + "sequencer-sierracompiler-config", + "sequencer-sierracompiler-deployment", + ) + + def __init__(self, config_map_name: str, pod_name: str) -> None: + self.config_map_name = config_map_name + self.pod_name = pod_name + + +class NamespaceAndInstructionArgs: + def __init__( + self, + namespace_list: list[str], + cluster_list: Optional[list[str]], + instruction_list: Optional[list[str]] = None, + ): + assert ( + namespace_list is not None and len(namespace_list) > 0 + ), "Namespace list cannot be None or empty." + self.namespace_list = namespace_list + assert cluster_list is None or len(cluster_list) == len( + namespace_list + ), "cluster_list must have the same length as namespace_list" + self.cluster_list = cluster_list + assert instruction_list is None or len(namespace_list) == len( + instruction_list + ), "instruction_list must have the same length as namespace_list" + self.instruction_list = instruction_list + + def size(self) -> int: + return len(self.namespace_list) + + def get_namespace(self, index: int) -> str: + return self.namespace_list[index] + + def get_cluster(self, index: int) -> Optional[str]: + return self.cluster_list[index] if self.cluster_list is not None else None + + def get_instruction(self, index: int) -> Optional[str]: + return self.instruction_list[index] if self.instruction_list is not None else None + + @staticmethod + def get_namespace_list_from_args( + args: argparse.Namespace, + ) -> list[str]: + """Get a list of namespaces based on the arguments""" + if args.namespace_list: + return args.namespace_list + + return [ + f"{args.namespace_prefix}-{i}" + for i in range(args.start_index, args.start_index + args.num_nodes) + ] + + @staticmethod + def get_context_list_from_args( + args: argparse.Namespace, + ) -> list[str]: + """Get a list of contexts based on the arguments""" + if args.cluster_list: + return args.cluster_list + + if args.cluster_prefix is None: + return None + + return [ + f"{args.cluster_prefix}-{i}" + for i in range(args.start_index, args.start_index + args.num_nodes) + ] + + +def get_namespace_args(namespace: str, cluster: Optional[str] = None) -> list[str]: + ret = ["-n", f"{namespace}"] + if cluster: + ret.extend(["--context", f"{cluster}"]) + return ret + + +def run_kubectl_command(args: list, capture_output: bool = True) -> subprocess.CompletedProcess: + full_command = ["kubectl"] + args + try: + result = subprocess.run(full_command, capture_output=capture_output, text=True, check=True) + return result + except subprocess.CalledProcessError as e: + print_error(f"kubectl command failed: {' '.join(full_command)}") + print_error(f"Error: {e.stderr}") + sys.exit(1) + + +def ask_for_confirmation() -> bool: + """Ask user for confirmation to proceed""" + response = ( + input(f"{Colors.BLUE.value}Do you approve these changes? (y/n){Colors.RESET.value}") + .strip() + .lower() + ) + return response == "y" + + +def wait_until_y_or_n(question: str) -> bool: + """Wait until user enters y or n. Cotinues asking until user enters y or n.""" + while True: + response = input(f"{Colors.BLUE.value}{question} (y/n){Colors.RESET.value}").strip().lower() + if response == "y" or response == "n": + break + print_error(f"Invalid response: {response}") + return response == "y" diff --git a/scripts/prod/metrics_lib.py b/scripts/prod/metrics_lib.py new file mode 100644 index 00000000000..701891b5d18 --- /dev/null +++ b/scripts/prod/metrics_lib.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +from time import sleep +from typing import Any, Callable, Optional + +import signal +import socket +import urllib.error +import urllib.request +from common_lib import Colors, get_namespace_args, print_colored, print_error +from prometheus_client.parser import text_string_to_metric_families + + +class MetricConditionGater: + """Gates progress on a metric satisfying a condition. + + This class was meant to be used with counter/gauge metrics. It may not work properly with histogram metrics. + """ + + class Metric: + def __init__( + self, + name: str, + value_condition: Callable[[Any], bool], + condition_description: Optional[str] = None, + ): + self.name = name + self.value_condition = value_condition + self.condition_description = condition_description + + def __init__( + self, + metric: "MetricConditionGater.Metric", + namespace: str, + cluster: Optional[str], + pod: str, + metrics_port: int, + refresh_interval_seconds: int = 3, + ): + self.metric = metric + self.local_port = self._get_free_port() + self.namespace = namespace + self.cluster = cluster + self.pod = pod + self.metrics_port = metrics_port + self.refresh_interval_seconds = refresh_interval_seconds + + @staticmethod + def _get_free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + return s.getsockname()[1] + + def _get_metrics_raw_string(self) -> str: + while True: + try: + with urllib.request.urlopen( + f"http://localhost:{self.local_port}/monitoring/metrics" + ) as response: + if response.status == 200: + return response.read().decode("utf-8") + else: + print_colored( + f"Failed to get metrics for pod {self.pod}: {response.status}" + ) + except urllib.error.URLError as e: + print_colored(f"Failed to get metrics for pod {self.pod}: {e}") + print_colored( + f"Waiting {self.refresh_interval_seconds} seconds to retry getting metrics...", + Colors.YELLOW, + ) + sleep(self.refresh_interval_seconds) + + def _poll_until_condition_met(self): + """Poll metrics until the condition is met for the metric.""" + condition_description = ( + f"({self.metric.condition_description}) " + if self.metric.condition_description is not None + else "" + ) + + while True: + metrics = self._get_metrics_raw_string() + assert metrics is not None, f"Failed to get metrics from for pod {self.pod}" + + metric_families = text_string_to_metric_families(metrics) + val = None + for metric_family in metric_families: + if metric_family.name == self.metric.name: + if len(metric_family.samples) > 1: + print_error( + f"Multiple samples found for metric {self.metric.name}. Using the first one.", + ) + val = metric_family.samples[0].value + break + + if val is None: + print_colored( + f"Metric '{self.metric.name}' not found in pod {self.pod}. Assuming the node is not ready." + ) + elif self.metric.value_condition(val): + print_colored( + f"Metric {self.metric.name} condition {condition_description}met (value={val})." + ) + return + else: + print_colored( + f"Metric {self.metric.name} condition {condition_description}not met (value={val}). Continuing to wait." + ) + + sleep(self.refresh_interval_seconds) + + @staticmethod + def _terminate_port_forward_process(pf_process: subprocess.Popen): + if pf_process and pf_process.poll() is None: + print_colored(f"Terminating kubectl port-forward process (PID: {pf_process.pid})") + pf_process.terminate() + try: + pf_process.wait(timeout=5) + except subprocess.TimeoutExpired: + print_colored("Force killing kubectl port-forward process") + pf_process.kill() + pf_process.wait() + + def gate(self): + """Wait until the nodes metrics satisfy the condition.""" + # This method: + # 1. Starts kubectl port forwarding to the node and keep it running in the background so we can access the metrics. + # 2. Calls _poll_until_condition_met. + # 3. Terminates the port forwarding process when done or when interrupted. + cmd = [ + "kubectl", + "port-forward", + f"pod/{self.pod}", + f"{self.local_port}:{self.metrics_port}", + ] + cmd.extend(get_namespace_args(self.namespace, self.cluster)) + + pf_process = None + + try: + pf_process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + print("Waiting for forwarding to start") + # Give the forwarding time to start. + # TODO(guy.f): Consider poll until the forwarding is ready if we see any issues. + sleep(3) + assert ( + pf_process.poll() is None + ), f"Port forwarding process exited with code {pf_process.returncode}" + + print( + f"Forwarding started (from local port {self.local_port} to {self.pod}:{self.metrics_port})" + ) + + # Set up signal handler to ensure forwarding subprocess is terminated on interruption + def signal_handler(signum, frame): + self._terminate_port_forward_process(pf_process) + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + self._poll_until_condition_met() + + finally: + self._terminate_port_forward_process(pf_process) diff --git a/scripts/prod/restart_all_cores.py b/scripts/prod/restart_all_cores.py new file mode 100755 index 00000000000..b143fe53381 --- /dev/null +++ b/scripts/prod/restart_all_cores.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 + +from typing import Optional + +import urllib.parse +from common_lib import NamespaceAndInstructionArgs, RestartStrategy, Service, print_colored +from restarter_lib import ServiceRestarter +from update_config_and_restart_nodes_lib import ( + ApolloArgsParserBuilder, + ConstConfigValuesUpdater, + get_configmap, + get_current_block_number, + get_logs_explorer_url, + parse_config_from_yaml, + update_config_and_restart_nodes, +) + + +# TODO(guy.f): Remove this once we have metrics we use to decide based on. +def get_logs_explorer_url_for_proposal( + namespace: str, + validator_id: str, + min_block_number: int, + project_name: str, +) -> str: + # Remove the 0x prefix from the validator id to get the number. + validator_id = validator_id[2:] + + query = ( + f'resource.labels.namespace_name:"{urllib.parse.quote(namespace)}"\n' + f'resource.labels.container_name="sequencer-core"\n' + f'textPayload =~ "DECISION_REACHED:.*proposer 0x0*{validator_id}"\n' + f'CAST(REGEXP_EXTRACT(textPayload, "height: (\\\\d+)"), "INT64") > {min_block_number}' + ) + return get_logs_explorer_url(query, project_name) + + +def get_validator_id(namespace: str, context: Optional[str]) -> str: + # Get current config and normalize it (e.g. " vs ') to ensure not showing bogus diffs. + original_config = get_configmap(namespace, context, Service.Core) + _, config_data = parse_config_from_yaml(original_config) + + return config_data["validator_id"] + + +class NodeValidatorIdCompositeUpdater(ConstConfigValuesUpdater): + def __init__(self, config_overrides: dict[str, any], validator_id_start_from: int): + super().__init__(config_overrides) + self.validator_id_start_from = validator_id_start_from + + def get_updated_config_for_instance( + self, config_data: dict[str, any], instance_index: int + ) -> dict[str, any]: + updated_config = super().get_updated_config_for_instance(config_data, instance_index) + validator_id_as_hex = hex(self.validator_id_start_from + instance_index) + updated_config["validator_id"] = validator_id_as_hex + return updated_config + + +def main(): + usage_example = """ +Examples: + # Restart all nodes at once. + %(prog)s --namespace-prefix apollo-sepolia-integration --num-nodes 3 --feeder-url feeder.integration-sepolia.starknet.io --project-name my-gcp-project + %(prog)s -n apollo-sepolia-integration -m 3 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + + # Restart nodes with cluster prefix + %(prog)s -n apollo-sepolia-integration -m 3 -c my-cluster -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + + # Restart nodes starting from specific node index + %(prog)s -n apollo-sepolia-integration -m 3 -s 5 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + + # Use different feeder URL + %(prog)s -n apollo-sepolia-integration -m 3 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + + # Use namespace list instead of prefix (restart specific namespaces) + %(prog)s --namespace-list apollo-sepolia-integration-0 apollo-sepolia-integration-2 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + %(prog)s -N apollo-sepolia-integration-0 apollo-sepolia-integration-2 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + + # Use cluster list for multiple clusters (only works with namespace-list, not namespace-prefix) + %(prog)s -N apollo-sepolia-integration-0 apollo-sepolia-integration-1 -C cluster1 cluster2 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + %(prog)s --namespace-list apollo-sepolia-integration-0 apollo-sepolia-integration-1 --cluster-list cluster1 cluster2 -f feeder.integration-sepolia.starknet.io --project-name my-gcp-project + """ + + args_builder = ApolloArgsParserBuilder( + "Restart all nodes using the value from the feeder URL", + usage_example, + include_restart_strategy=False, + ) + + args_builder.add_argument( + "-f", + "--feeder-url", + required=True, + type=str, + help="The feeder URL to get the current block from", + ) + + # TODO(guy.f): Remove this when we rely on metrics for restarting. + args_builder.add_argument( + "--project-name", + required=True, + help="The name of the project to get logs from.", + ) + + args_builder.add_argument( + "--validator-id-start-from", + type=int, + help="If set, also update the validator ID config to this value + index of the instance being restarted. Value is in decimal format.", + ) + + args = args_builder.build() + + # Get current block number from feeder URL + current_block_number = get_current_block_number(args.feeder_url) + next_block_number = current_block_number + 1 + + print_colored(f"Current block number: {current_block_number}") + print_colored(f"Next block number: {next_block_number}") + + config_overrides = { + "consensus_manager_config.cende_config.skip_write_height": next_block_number, + "consensus_manager_config.immediate_active_height": next_block_number, + } + + namespace_list = NamespaceAndInstructionArgs.get_namespace_list_from_args(args) + context_list = NamespaceAndInstructionArgs.get_context_list_from_args(args) + + # Generate logs explorer URLs if needed + post_restart_instructions = [] + + for namespace, context in zip(namespace_list, context_list or [None] * len(namespace_list)): + url = get_logs_explorer_url_for_proposal( + namespace, + get_validator_id(namespace, context), + # Feeder could be behind by up to 10 blocks, so we add 10 to the current block number. + current_block_number + 10, + args.project_name, + ) + post_restart_instructions.append( + f"Please check logs and verify that the node has proposed a block that was accepted. Logs URL: {url}" + ) + + namespace_and_instruction_args = NamespaceAndInstructionArgs( + namespace_list, + context_list, + post_restart_instructions, + ) + restarter = ServiceRestarter.from_restart_strategy( + RestartStrategy.ALL_AT_ONCE, + namespace_and_instruction_args, + Service.Core, + ) + + updater = ( + NodeValidatorIdCompositeUpdater(config_overrides, args.validator_id_start_from) + if args.validator_id_start_from + else ConstConfigValuesUpdater(config_overrides) + ) + + update_config_and_restart_nodes( + updater, + namespace_and_instruction_args, + Service.Core, + restarter, + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/prod/restart_all_nodes_together.py b/scripts/prod/restart_all_nodes_together.py deleted file mode 100755 index d2953198eba..00000000000 --- a/scripts/prod/restart_all_nodes_together.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import json -import sys -from enum import Enum -from typing import Optional - -import urllib.error -import urllib.parse -import urllib.request -from update_config_and_restart_nodes_lib import ( - ApolloArgsParserBuilder, - Service, - get_configmap, - get_context_list_from_args, - get_logs_explorer_url, - get_namespace_list_from_args, - parse_config_from_yaml, - print_colored, - print_error, - update_config_and_restart_nodes, -) - - -# TODO(guy.f): Remove this once we have metrics we use to decide based on. -def get_logs_explorer_url_for_proposal( - namespace: str, - validator_id: str, - min_block_number: int, - project_name: str, -) -> str: - # Remove the 0x prefix from the validator id to get the number. - validator_id = validator_id[2:] - - query = ( - f'resource.labels.namespace_name:"{urllib.parse.quote(namespace)}"\n' - f'resource.labels.container_name="sequencer-core"\n' - f'textPayload =~ "DECISION_REACHED:.*proposer 0x0*{validator_id}"\n' - f'CAST(REGEXP_EXTRACT(textPayload, "height: (\\\\d+)"), "INT64") > {min_block_number}' - ) - return get_logs_explorer_url(query, project_name) - - -class RestartStrategy(Enum): - """Strategy for restarting nodes.""" - - ALL_AT_ONCE = "all_at_once" - ONE_BY_ONE = "one_by_one" - - -def restart_strategy_converter(strategy_name: str) -> RestartStrategy: - """Convert string to RestartStrategy enum with informative error message""" - RESTART_STRATEGY_PREFIX = f"{RestartStrategy.__name__}." - if strategy_name.startswith(RESTART_STRATEGY_PREFIX): - strategy_name = strategy_name[len(RESTART_STRATEGY_PREFIX) :] - - try: - return RestartStrategy(strategy_name) - except KeyError: - valid_strategies = ", ".join([strategy.value for strategy in RestartStrategy]) - raise argparse.ArgumentTypeError( - f"Invalid restart strategy '{strategy_name}'. Valid options are: {valid_strategies}" - ) - - -def get_validator_id(namespace: str, context: Optional[str]) -> str: - # Get current config and normalize it (e.g. " vs ') to ensure not showing bogus diffs. - original_config = get_configmap(namespace, context, Service.Core) - _, config_data = parse_config_from_yaml(original_config) - - return config_data["validator_id"] - - -def main(): - usage_example = """ -Examples: - # Restart all nodes to at the next block after current feeder block (default: One_By_One strategy) - %(prog)s --namespace-prefix apollo-sepolia-integration --num-nodes 3 --feeder_url feeder.integration-sepolia.starknet.io - %(prog)s -n apollo-sepolia-integration -m 3 -f feeder.integration-sepolia.starknet.io - - # Restart nodes one by one with project name for showing logs link - %(prog)s -n apollo-sepolia-integration -m 3 -f feeder.integration-sepolia.starknet.io -t One_By_One --project-name my-gcp-project - - # Restart nodes with cluster prefix - %(prog)s -n apollo-sepolia-integration -m 3 -c my-cluster -f feeder.integration-sepolia.starknet.io - - # Update configuration without restarting nodes - %(prog)s -n apollo-sepolia-integration -m 3 -f feeder.integration-sepolia.starknet.io --no-restart - - # Restart nodes starting from specific node index - %(prog)s -n apollo-sepolia-integration -m 3 -s 5 -f feeder.integration-sepolia.starknet.io - - # Use different feeder URL - %(prog)s -n apollo-sepolia-integration -m 3 -f feeder.integration-sepolia.starknet.io - - # Use namespace list instead of prefix (restart specific namespaces) - %(prog)s --namespace-list apollo-sepolia-integration-0 apollo-sepolia-integration-2 -f feeder.integration-sepolia.starknet.io - %(prog)s -N apollo-sepolia-integration-0 apollo-sepolia-integration-2 -f feeder.integration-sepolia.starknet.io - - # Use cluster list for multiple clusters (only works with namespace-list, not namespace-prefix) - %(prog)s -N apollo-sepolia-integration-0 apollo-sepolia-integration-1 -C cluster1 cluster2 -f feeder.integration-sepolia.starknet.io - %(prog)s --namespace-list apollo-sepolia-integration-0 apollo-sepolia-integration-1 --cluster-list cluster1 cluster2 -f feeder.integration-sepolia.starknet.io - """ - - args_builder = ApolloArgsParserBuilder( - "Restart all nodes using the value from the feeder URL", usage_example - ) - - args_builder.add_argument( - "-f", - "--feeder_url", - required=True, - type=str, - help="The feeder URL to get the current block from", - ) - - args_builder.add_argument( - "-t", - "--restart-strategy", - type=restart_strategy_converter, - choices=list(RestartStrategy), - default=RestartStrategy.ONE_BY_ONE, - help="Strategy for restarting nodes (default: All_At_Once)", - ) - - # TODO(guy.f): Remove this when we rely on metrics for restarting. - args_builder.add_argument( - "--project-name", - help="The name of the project to get logs from. If One_By_One strategy is used, this is required.", - ) - - args = args_builder.build() - - if args.restart_strategy == RestartStrategy.ONE_BY_ONE and args.project_name is None: - print_error("Error: --project-name is required when using One_By_One strategy") - sys.exit(1) - - # Get current block number from feeder URL - try: - url = f"https://{args.feeder_url}/feeder_gateway/get_block" - with urllib.request.urlopen(url) as response: - if response.status != 200: - raise urllib.error.HTTPError( - url, response.status, "HTTP Error", response.headers, None - ) - data = json.loads(response.read().decode("utf-8")) - current_block_number = data["block_number"] - next_block_number = current_block_number + 1 - - print_colored(f"Current block number: {current_block_number}") - print_colored(f"Next block number: {next_block_number}") - - except urllib.error.URLError as e: - print_error(f"Failed to fetch block number from feeder URL: {e}") - sys.exit(1) - except KeyError as e: - print_error(f"Unexpected response format from feeder URL: {e}") - sys.exit(1) - except json.JSONDecodeError as e: - print_error(f"Failed to parse JSON response from feeder URL: {e}") - sys.exit(1) - - config_overrides = { - "consensus_manager_config.immediate_active_height": next_block_number, - "consensus_manager_config.cende_config.skip_write_height": next_block_number, - } - - namespace_list = get_namespace_list_from_args(args) - context_list = get_context_list_from_args(args) - if context_list is not None: - assert len(namespace_list) == len( - context_list - ), "namespace_list and context_list must have the same length" - - # Generate logs explorer URLs if needed - post_restart_instructions = [] - if args.restart_strategy == RestartStrategy.ONE_BY_ONE: - for namespace, context in zip(namespace_list, context_list or [None] * len(namespace_list)): - url = get_logs_explorer_url_for_proposal( - namespace, - get_validator_id(namespace, context), - # Feeder could be behind by up to 10 blocks, so we add 10 to the current block number. - current_block_number + 10, - args.project_name, - ) - post_restart_instructions.append( - f"Please check logs and verify that the node has proposed a block that was accepted. Logs URL: {url}" - ) - - update_config_and_restart_nodes( - config_overrides, - namespace_list, - Service.Core, - context_list, - not args.no_restart, - args.restart_strategy == RestartStrategy.ONE_BY_ONE, - post_restart_instructions, - ) - - -if __name__ == "__main__": - main() diff --git a/scripts/prod/restarter_lib.py b/scripts/prod/restarter_lib.py new file mode 100644 index 00000000000..e8c7a351a43 --- /dev/null +++ b/scripts/prod/restarter_lib.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 + +import sys +from abc import ABC, abstractmethod +from time import sleep +from typing import Callable, Optional + +from common_lib import ( + Colors, + NamespaceAndInstructionArgs, + RestartStrategy, + Service, + get_namespace_args, + print_colored, + print_error, + run_kubectl_command, + wait_until_y_or_n, +) +from metrics_lib import MetricConditionGater + + +def _get_pod_names( + namespace: str, service: Service, index: int, cluster: Optional[str] = None +) -> list[str]: + kubectl_args = [ + "get", + "pods", + "-o", + "name", + ] + kubectl_args.extend(get_namespace_args(namespace, cluster)) + pods = run_kubectl_command(kubectl_args, capture_output=True).stdout.splitlines() + return [pod.split("/")[1] for pod in pods if pod.startswith(f"pod/{service.pod_name}")] + + +class ServiceRestarter(ABC): + """Abstract class for restarting service instances.""" + + def __init__( + self, + namespace_and_instruction_args: NamespaceAndInstructionArgs, + service: Service, + ): + self.namespace_and_instruction_args = namespace_and_instruction_args + self.service = service + + @staticmethod + def _restart_pod( + namespace: str, service: Service, index: int, cluster: Optional[str] = None + ) -> None: + """Restart pod by deleting it""" + pods = _get_pod_names(namespace, service, index, cluster) + + if not pods: + print_error( + f"Could not find pods for service {service.pod_name} with namespace {namespace} and cluster {cluster}." + ) + sys.exit(1) + + # Go over each pod and delete it. + for pod in pods: + kubectl_args = [ + "delete", + "pod", + pod, + ] + kubectl_args.extend(get_namespace_args(namespace, cluster)) + + try: + run_kubectl_command(kubectl_args, capture_output=False) + print_colored(f"Restarted {pod} for node {index}") + except Exception as e: + print_error(f"Failed restarting {pod} for node {index}: {e}") + sys.exit(1) + + @abstractmethod + def restart_service(self, instance_index: int) -> bool: + """Restart service for a specific instance. If returns False, the restart process should be aborted.""" + + # from_restart_strategy is a static method that returns the appropriate ServiceRestarter based on the restart strategy. + @staticmethod + def from_restart_strategy( + restart_strategy: RestartStrategy, + namespace_and_instruction_args: NamespaceAndInstructionArgs, + service: Service, + ) -> "ServiceRestarter": + if restart_strategy == RestartStrategy.ONE_BY_ONE: + check_between_restarts = lambda instance_index: ( + True + if instance_index == namespace_and_instruction_args.size() - 1 + else wait_until_y_or_n(f"Do you want to restart the next pod?") + ) + + return ChecksBetweenRestartsCompositeRestarter( + namespace_and_instruction_args, + service, + check_between_restarts, + RestartPodOnlyRestarter(namespace_and_instruction_args, service), + ) + elif restart_strategy == RestartStrategy.ALL_AT_ONCE: + return ChecksBetweenRestartsCompositeRestarter( + namespace_and_instruction_args, + service, + lambda instance_index: True, + RestartPodOnlyRestarter(namespace_and_instruction_args, service), + ) + elif restart_strategy == RestartStrategy.NO_RESTART: + assert ( + namespace_and_instruction_args.get_instruction(0) is None + ), f"post_restart_instructions is not allowed with no_restart as the restart strategy" + return NoOpServiceRestarter(namespace_and_instruction_args, service) + else: + raise ValueError(f"Invalid restart strategy: {restart_strategy}") + + +class RestartPodOnlyRestarter(ServiceRestarter): + """Restarter that only restarts the pod and does not check anything else.""" + + def __init__( + self, namespace_and_instruction_args: NamespaceAndInstructionArgs, service: Service + ): + super().__init__(namespace_and_instruction_args, service) + + def restart_service(self, instance_index: int) -> bool: + """Restarts the pod and does nothing else.""" + self._restart_pod( + self.namespace_and_instruction_args.get_namespace(instance_index), + self.service, + instance_index, + self.namespace_and_instruction_args.get_cluster(instance_index), + ) + print_colored(f"Restarted pod {instance_index}. ", Colors.YELLOW) + return True + + +class ChecksBetweenRestartsCompositeRestarter(ServiceRestarter): + """Checks between restarts.""" + + def __init__( + self, + namespace_and_instruction_args: NamespaceAndInstructionArgs, + service: Service, + check_between_restarts: Callable[[int], bool], + base_service_restarter: ServiceRestarter, + ): + super().__init__(namespace_and_instruction_args, service) + self.check_between_restarts = check_between_restarts + self.base_service_restarter = base_service_restarter + + def restart_service(self, instance_index: int) -> bool: + """Call the base restarter on each instance one by one, running the check_between_restarts in between each.""" + self.base_service_restarter.restart_service(instance_index) + + instructions = self.namespace_and_instruction_args.get_instruction(instance_index) + if instructions is not None: + print_colored(f"{instructions} ", Colors.YELLOW) + return self.check_between_restarts(instance_index) + + +class NoOpServiceRestarter(ServiceRestarter): + """No-op service restarter.""" + + def restart_service(self, instance_index: int) -> bool: + """No-op.""" + print_colored("\nSkipping pod restart.") + return True + + +class WaitOnMetricRestarter(ChecksBetweenRestartsCompositeRestarter): + def __init__( + self, + namespace_and_instruction_args: NamespaceAndInstructionArgs, + service: Service, + metrics: list["MetricConditionGater.Metric"], + metrics_port: int, + restart_strategy: RestartStrategy, + ): + self.metrics = metrics + self.metrics_port = metrics_port + if restart_strategy == RestartStrategy.ONE_BY_ONE: + check_function = self._check_between_each_restart + base_restarter = RestartPodOnlyRestarter(namespace_and_instruction_args, service) + elif restart_strategy == RestartStrategy.ALL_AT_ONCE: + check_function = self._check_all_only_after_last_restart + base_restarter = RestartPodOnlyRestarter(namespace_and_instruction_args, service) + elif restart_strategy == RestartStrategy.NO_RESTART: + check_function = self._check_between_each_restart + base_restarter = NoOpServiceRestarter(namespace_and_instruction_args, service) + else: + print_error(f"Invalid restart strategy: {restart_strategy} for WaitOnMetricRestarter.") + sys.exit(1) + + super().__init__(namespace_and_instruction_args, service, check_function, base_restarter) + + def _check_between_each_restart(self, instance_index: int) -> bool: + if not self._wait_for_pod_to_satisfy_condition(instance_index): + print_error(f"Failed waiting for condition(s) for Pod {instance_index}.") + if instance_index == self.namespace_and_instruction_args.size() - 1: + # Last instance, no need to prompt the user about the next restart. + return True + return wait_until_y_or_n(f"Do you want to restart the next pod?") + + def _check_all_only_after_last_restart(self, instance_index: int) -> bool: + # Restart all nodes without waiting for confirmation. + if instance_index < self.namespace_and_instruction_args.size() - 1: + return True + + # After the last node has been restarted, wait for all pods to satisfy the condition. + for instance_index in range(self.namespace_and_instruction_args.size()): + if not self._wait_for_pod_to_satisfy_condition(instance_index): + print_error(f"Failed waiting for condition(s) for Pod {instance_index}.") + return True + + def _wait_for_pod_to_satisfy_condition(self, instance_index: int) -> bool: + # The sleep is to prevent the case where we get the pod name of the old pod we just deleted + # instead of the new one. + # TODO(guy.f): Verify this is not the name of the old pod some other way. + sleep(2) + pod_names = WaitOnMetricRestarter._wait_for_pods_to_be_ready( + self.namespace_and_instruction_args.get_namespace(instance_index), + self.namespace_and_instruction_args.get_cluster(instance_index), + self.service, + ) + if pod_names is None: + return False + + for pod_name in pod_names: + for metric in self.metrics: + metric_condition_gater = MetricConditionGater( + metric, + self.namespace_and_instruction_args.get_namespace(instance_index), + self.namespace_and_instruction_args.get_cluster(instance_index), + pod_name, + self.metrics_port, + ) + metric_condition_gater.gate() + + @staticmethod + def _wait_for_pods_to_be_ready( + namespace: str, + cluster: Optional[str], + service: Service, + wait_timeout: int = 180, + num_retry: int = 3, + refresh_delay_sec: int = 3, + ) -> Optional[list[str]]: + """ + Wait for pods to be in ready mode as reported by Kubernetes. + """ + + for i in range(num_retry): + pods = _get_pod_names(namespace, service, 0, cluster) + if pods: + for pod in pods: + print_colored( + f"Waiting for pod {pod} to be ready... (timeout set to {wait_timeout}s)" + ) + kubectl_args = [ + "wait", + "--for=condition=ready", + f"pod/{pod}", + "--timeout", + f"{wait_timeout}s", + ] + kubectl_args.extend(get_namespace_args(namespace, cluster)) + result = run_kubectl_command(kubectl_args, capture_output=False) + + if result.returncode != 0: + print_colored( + f"Timed out waiting for pod {pod} to be ready: {result.stderr}, retrying... (attempt {i + 1}/{num_retry})", + Colors.YELLOW, + ) + break + return pods + else: + print_colored( + f"Could not get pod names for service {service.pod_name}, retrying... (attempt {i + 1}/{num_retry})", + Colors.YELLOW, + ) + sleep(refresh_delay_sec) + + print_error(f"Pods for service {service.pod_name} are not ready after {num_retry} attempts") + return None diff --git a/scripts/prod/set_node_revert_mode.py b/scripts/prod/set_node_revert_mode.py index 6a0baa72d6c..e7465d1021e 100755 --- a/scripts/prod/set_node_revert_mode.py +++ b/scripts/prod/set_node_revert_mode.py @@ -1,93 +1,218 @@ #!/usr/bin/env python3 -import sys +from typing import Optional +import urllib.parse +from common_lib import ( + Colors, + NamespaceAndInstructionArgs, + RestartStrategy, + Service, + print_colored, +) +from metrics_lib import MetricConditionGater +from restarter_lib import ServiceRestarter, WaitOnMetricRestarter from update_config_and_restart_nodes_lib import ( ApolloArgsParserBuilder, - print_colored, - print_error, + ConstConfigValuesUpdater, + get_current_block_number, + get_logs_explorer_url, update_config_and_restart_nodes, ) +def get_logs_explorer_url_for_enable_revert( + namespace: str, + block_number: int, + project_name: str, +) -> str: + query = ( + f'resource.labels.namespace_name:"{urllib.parse.quote(namespace)}"\n' + f'resource.labels.container_name="sequencer-core"\n' + f'textPayload =~ "Done reverting.*storage up to height {block_number}"' + ) + return get_logs_explorer_url(query, project_name) + + +def set_revert_mode( + namespace_and_instruction_args: NamespaceAndInstructionArgs, + restarter: ServiceRestarter, + should_revert: bool, + revert_up_to_block: int, + immediate_active_height: Optional[int] = None, +): + config_overrides = { + "revert_config.should_revert": should_revert, + "revert_config.revert_up_to_and_including": revert_up_to_block, + } + if immediate_active_height is not None: + assert not should_revert, "Immediate active height should not be set when reverting" + # We need a short variable name to avoid splitting to multiple lines which local black + # formatting does in a way that CI black doesn't like and fails on. + height = immediate_active_height + config_overrides["consensus_manager_config.immediate_active_height"] = height + config_overrides["consensus_manager_config.cende_config.skip_write_height"] = height + + update_config_and_restart_nodes( + ConstConfigValuesUpdater(config_overrides), + namespace_and_instruction_args, + Service.Core, + restarter, + ) + + +def enable_revert_mode( + namespace_list: list[str], + context_list: Optional[list[str]], + project_name: str, + revert_up_to_block: int, +): + print_colored( + f"Enabling revert mode (reverting up to and including block {revert_up_to_block})", + Colors.YELLOW, + ) + post_restart_instructions = [] + for namespace in namespace_list: + url = get_logs_explorer_url_for_enable_revert(namespace, revert_up_to_block, project_name) + + post_restart_instructions.append( + f"Please check logs and verify that revert has completed (both in the batcher and for sync). Logs URL: {url}" + ) + + namespace_and_instruction_args = NamespaceAndInstructionArgs( + namespace_list, context_list, post_restart_instructions + ) + restarter = WaitOnMetricRestarter( + namespace_and_instruction_args, + Service.Core, + [ + MetricConditionGater.Metric( + "apollo_consensus_reverted_batcher_up_to_and_including", + lambda x: x == revert_up_to_block, + f"Waiting for the batcher to revert up to and including {revert_up_to_block}", + ), + MetricConditionGater.Metric( + "apollo_state_sync_reverted_up_to_and_including", + lambda x: x == revert_up_to_block, + f"Waiting for state sync to revert up to and including {revert_up_to_block}", + ), + ], + 8082, + RestartStrategy.ALL_AT_ONCE, + ) + set_revert_mode(namespace_and_instruction_args, restarter, True, revert_up_to_block) + + +def disable_revert_mode( + namespace_list: list[str], + context_list: Optional[list[str]], + immediate_active_height: int, +): + print_colored("Disabling revert mode", Colors.YELLOW) + namespace_and_instruction_args = NamespaceAndInstructionArgs(namespace_list, context_list) + + set_revert_mode( + namespace_and_instruction_args, + ServiceRestarter.from_restart_strategy( + RestartStrategy.ALL_AT_ONCE, namespace_and_instruction_args, Service.Core + ), + False, + # Setting to max block to max u64 to disable revert. + 2**64 - 1, + immediate_active_height, + ) + + def main(): usage_example = """ Examples: # Set revert mode up to a specific block - %(prog)s --namespace apollo-sepolia-integration --num-nodes 3 revert --revert_up_to_block 12345 - %(prog)s -n apollo-sepolia-integration -N 3 revert -b 12345 + %(prog)s --namespace apollo-sepolia-integration --num-nodes 3 --revert-only --revert_up_to_block 12345 + %(prog)s -n apollo-sepolia-integration -N 3 --revert-only -b 12345 + + # Set revert mode using feeder URL to get current block + %(prog)s --namespace apollo-sepolia-integration --num-nodes 3 --revert-only --feeder-url feeder.integration-sepolia.starknet.io + %(prog)s -n apollo-sepolia-integration -N 3 --revert-only -f feeder.integration-sepolia.starknet.io # Disable revert mode - %(prog)s --namespace apollo-sepolia-integration --num-nodes 3 disable-revert - %(prog)s -n apollo-sepolia-integration -N 3 disable-revert + %(prog)s --namespace apollo-sepolia-integration --num-nodes 3 --disable-revert-only + %(prog)s -n apollo-sepolia-integration -N 3 --disable-revert-only # Set revert mode with cluster prefix - %(prog)s -n apollo-sepolia-integration -N 3 -c my-cluster revert -b 12345 - - # Disable revert mode without restarting nodes - %(prog)s -n apollo-sepolia-integration -N 3 disable-revert --no-restart + %(prog)s -n apollo-sepolia-integration -N 3 -c my-cluster --revert-only -b 12345 - # Set revert mode with explicit restart - %(prog)s -n apollo-sepolia-integration -N 3 revert -b 12345 -r + # Set revert mode with feeder URL and cluster prefix + %(prog)s -n apollo-sepolia-integration -N 3 -c my-cluster --revert-only -f feeder.integration-sepolia.starknet.io # Set revert mode starting from specific node index - %(prog)s -n apollo-sepolia-integration -N 3 -i 5 revert -b 12345 + %(prog)s -n apollo-sepolia-integration -N 3 -i 5 --revert-only -b 12345 + + # Set revert mode with feeder URL starting from specific node index + %(prog)s -n apollo-sepolia-integration -N 3 -i 5 --revert-only -f feeder.integration-sepolia.starknet.io """ args_builder = ApolloArgsParserBuilder( - "Sets or unsets the revert mode for the sequencer nodes", usage_example + "Sets or unsets the revert mode for the sequencer nodes", + usage_example, + include_restart_strategy=False, ) - # Create subparsers for revert operations - subparsers = args_builder.parser.add_subparsers( - dest="command", help="Available commands", required=True + revert_group = args_builder.parser.add_mutually_exclusive_group() + revert_group.add_argument("--revert-only", action="store_true", help="Enable revert mode") + revert_group.add_argument( + "--disable-revert-only", action="store_true", help="Disable revert mode" ) - # Revert subcommand - revert_parser = subparsers.add_parser("revert", help="Enable revert mode") - revert_parser.add_argument( + block_revert_args_group = args_builder.parser.add_mutually_exclusive_group(required=True) + + block_revert_args_group.add_argument( "-b", - "--revert_up_to_block", + "--revert-up-to-block", type=int, - required=True, - help="Block number up to which to revert. Must be a positive integer.", + help="Block number up to which to revert (inclusive). Must be a positive integer.", + ) + + block_revert_args_group.add_argument( + "-f", + "--feeder-url", + type=str, + help="The feeder URL to get the current block from. We will revert all blocks above it.", ) - # No-revert subcommand - subparsers.add_parser("disable-revert", help="Disable revert mode") + # TODO(guy.f): Remove this when we rely on metrics for restarting. + args_builder.add_argument( + "--project-name", + required=True, + help="The name of the project to get logs from. If One_By_One strategy is used, this is required.", + ) args = args_builder.build() - # Validate block number for revert command - if args.command == "revert": - if args.revert_up_to_block <= 0: - print_error("Error: --revert_up_to_block (-b) must be a positive integer") - sys.exit(1) - - # Add revert-specific configuration based on subcommand - if args.command == "revert": - should_revert = True - revert_up_to_block = args.revert_up_to_block - print_colored( - f"\nEnabling revert mode up to (and including) block {args.revert_up_to_block}" - ) - elif args.command == "disable-revert": - should_revert = False - revert_up_to_block = 18446744073709551615 # Max unit64. - print_colored(f"\nDisabling revert mode") - config_overrides = { - "revert_config.should_revert": should_revert, - "revert_config.revert_up_to_and_including": revert_up_to_block, - } + namespace_list = NamespaceAndInstructionArgs.get_namespace_list_from_args(args) + context_list = NamespaceAndInstructionArgs.get_context_list_from_args(args) - update_config_and_restart_nodes( - config_overrides, - args.namespace, - args.num_nodes, - args.start_index, - args.cluster, - not args.no_restart, + should_revert = not args.disable_revert_only + should_disable_revert = not args.revert_only + revert_up_to_block = ( + args.revert_up_to_block + if args.revert_up_to_block is not None + else get_current_block_number(args.feeder_url) ) + if should_revert: + enable_revert_mode( + namespace_list, + context_list, + args.project_name, + revert_up_to_block, + ) + + if should_disable_revert: + disable_revert_mode( + namespace_list, + context_list, + # Immediate active height is the block number which will be the first block proposed. + revert_up_to_block, + ) if __name__ == "__main__": diff --git a/scripts/prod/update_config_and_restart_nodes.py b/scripts/prod/update_config_and_restart_nodes.py index 6b07d612e61..5d852941f0f 100755 --- a/scripts/prod/update_config_and_restart_nodes.py +++ b/scripts/prod/update_config_and_restart_nodes.py @@ -5,14 +5,18 @@ import sys from typing import Any -from update_config_and_restart_nodes_lib import ( - ApolloArgsParserBuilder, +from common_lib import ( Colors, + NamespaceAndInstructionArgs, Service, - get_context_list_from_args, - get_namespace_list_from_args, print_colored, print_error, +) +from metrics_lib import MetricConditionGater +from restarter_lib import ServiceRestarter, WaitOnMetricRestarter +from update_config_and_restart_nodes_lib import ( + ApolloArgsParserBuilder, + ConstConfigValuesUpdater, update_config_and_restart_nodes, ) @@ -83,35 +87,35 @@ def main(): usage_example = """ Examples: # Basic usage with namespace prefix and node count - %(prog)s -n apollo-sepolia-integration -m 3 --config-overrides consensus_manager_config.timeout=5000 --config-overrides validator_id=0x42 + %(prog)s -n apollo-sepolia-integration -m 3 -t all_at_once --config-overrides consensus_manager_config.timeout=5000 --config-overrides validator_id=0x42 # Using namespace list mode (no num-nodes or start-index allowed) - %(prog)s -N apollo-sepolia-test-0 apollo-sepolia-test-1 apollo-sepolia-test-2 --config-overrides consensus_manager_config.timeout=5000 + %(prog)s -N apollo-sepolia-test-0 apollo-sepolia-test-1 apollo-sepolia-test-2 -t one_by_one --config-overrides consensus_manager_config.timeout=5000 # Using cluster prefix with namespace prefix - %(prog)s -n apollo-sepolia-integration -m 3 -c my-cluster --config-overrides validator_id=0x42 + %(prog)s -n apollo-sepolia-integration -m 3 -c my-cluster -t all_at_once --config-overrides validator_id=0x42 # Using cluster list with namespace list (must have same number of items) - %(prog)s -N apollo-sepolia-test-0 apollo-sepolia-test-2 -C cluster0 cluster2 --config-overrides validator_id=0x42 + %(prog)s -N apollo-sepolia-test-0 apollo-sepolia-test-2 -C cluster0 cluster2 -t one_by_one --config-overrides validator_id=0x42 # Update different service types - %(prog)s -n apollo-sepolia-integration -m 3 -j Gateway --config-overrides gateway_config.port=8080 - %(prog)s -n apollo-sepolia-integration -m 3 -j Mempool --config-overrides mempool_config.max_size=1000 - %(prog)s -n apollo-sepolia-integration -m 3 -j L1 --config-overrides l1_config.endpoint=\"https://eth-mainnet.alchemyapi.io/v2/your-key\" - %(prog)s -n apollo-sepolia-integration -m 3 -j HttpServer --config-overrides http_server_config.port=8081 - %(prog)s -n apollo-sepolia-integration -m 3 -j SierraCompiler --config-overrides sierra_compiler_config.timeout=30000 + %(prog)s -n apollo-sepolia-integration -m 3 -t all_at_once -j Gateway --config-overrides gateway_config.port=8080 + %(prog)s -n apollo-sepolia-integration -m 3 -t one_by_one -j Mempool --config-overrides mempool_config.max_size=1000 + %(prog)s -n apollo-sepolia-integration -m 3 -t all_at_once -j L1 --config-overrides l1_config.endpoint=\"https://eth-mainnet.alchemyapi.io/v2/your-key\" + %(prog)s -n apollo-sepolia-integration -m 3 -t one_by_one -j HttpServer --config-overrides http_server_config.port=8081 + %(prog)s -n apollo-sepolia-integration -m 3 -t all_at_once -j SierraCompiler --config-overrides sierra_compiler_config.timeout=30000 # Update starting from specific node index - %(prog)s -n apollo-sepolia-integration -m 3 -s 5 --config-overrides validator_id=0x42 + %(prog)s -n apollo-sepolia-integration -m 3 -s 5 -t one_by_one --config-overrides validator_id=0x42 # Update without restart - %(prog)s -n apollo-sepolia-integration -m 3 --config-overrides validator_id=0x42 --no-restart + %(prog)s -n apollo-sepolia-integration -m 3 -t no_restart --config-overrides validator_id=0x42 - # Update with explicit restart (default behavior) - %(prog)s -n apollo-sepolia-integration -m 3 --config-overrides validator_id=0x42 -r + # Update with explicit restart (all at once) + %(prog)s -n apollo-sepolia-integration -m 3 -t all_at_once --config-overrides validator_id=0x42 # Complex example with multiple config overrides - %(prog)s -n apollo-sepolia-integration -m 3 -c my-cluster -j Core --config-overrides consensus_manager_config.timeout=5000 --config-overrides validator_id=0x42 --config-overrides components.gateway.url=\"localhost\" + %(prog)s -n apollo-sepolia-integration -m 3 -c my-cluster -t one_by_one -j Core --config-overrides consensus_manager_config.timeout=5000 --config-overrides validator_id=0x42 --config-overrides components.gateway.url=\"localhost\" """ @@ -138,6 +142,12 @@ def main(): help="Service type to operate on; determines configmap and pod names (default: Core)", ) + args_builder.add_argument( + "--no-check-for-good-proposal", + action="store_true", + help="If set, for restarts of Core (only), will not stop to check that a new proposal succeeded post restarts before continuing.", + ) + args = args_builder.build() config_overrides = parse_config_overrides(args.config_overrides) @@ -149,12 +159,36 @@ def main(): print_error("No config overrides provided") sys.exit(1) + namespace_and_instruction_args = NamespaceAndInstructionArgs( + NamespaceAndInstructionArgs.get_namespace_list_from_args(args), + NamespaceAndInstructionArgs.get_context_list_from_args(args), + None, + ) + + if args.service == Service.Core and not args.no_check_for_good_proposal: + restarter = WaitOnMetricRestarter( + namespace_and_instruction_args, + args.service, + [ + MetricConditionGater.Metric( + "consensus_decisions_reached_as_proposer", lambda x: x > 0 + ) + ], + metrics_port=8082, + restart_strategy=args.restart_strategy, + ) + else: + restarter = ServiceRestarter.from_restart_strategy( + args.restart_strategy, + namespace_and_instruction_args, + args.service, + ) + update_config_and_restart_nodes( - config_overrides, - get_namespace_list_from_args(args), + ConstConfigValuesUpdater(config_overrides), + namespace_and_instruction_args, args.service, - get_context_list_from_args(args), - not args.no_restart, + restarter, ) diff --git a/scripts/prod/update_config_and_restart_nodes_lib.py b/scripts/prod/update_config_and_restart_nodes_lib.py index 2c4b8c05545..680ae30ef0b 100755 --- a/scripts/prod/update_config_and_restart_nodes_lib.py +++ b/scripts/prod/update_config_and_restart_nodes_lib.py @@ -2,40 +2,35 @@ import argparse import json -import subprocess import sys -from enum import Enum +from abc import ABC, abstractmethod from typing import Any, Optional import tempfile -import urllib.parse +import urllib.error +import urllib.request import yaml +from common_lib import ( + Colors, + NamespaceAndInstructionArgs, + RestartStrategy, + Service, + ask_for_confirmation, + get_namespace_args, + print_colored, + print_error, + restart_strategy_converter, + run_kubectl_command, +) from difflib import unified_diff - - -class Colors(Enum): - """ANSI color codes for terminal output""" - - RED = "\033[1;31m" - GREEN = "\033[1;32m" - YELLOW = "\033[1;33m" - BLUE = "\033[1;34m" - RESET = "\033[0m" - - -def print_colored(message: str, color: Colors = Colors.RESET, file=sys.stdout) -> None: - """Print message with color""" - print(f"{color.value}{message}{Colors.RESET.value}", file=file) - - -def print_error(message: str) -> None: - print_colored(message, color=Colors.RED, file=sys.stderr) +from restarter_lib import ServiceRestarter class ApolloArgsParserBuilder: """Builder class for creating argument parsers with required flags and custom arguments.""" - def __init__(self, description: str, usage_example: str): + # TODO(guy.f): If we need to exclude more than just the restart flag, create a more generic mechanism. + def __init__(self, description: str, usage_example: str, include_restart_strategy: bool = True): """Initialize the builder with usage example for epilog. Args: @@ -48,10 +43,14 @@ def __init__(self, description: str, usage_example: str): epilog=usage_example, ) - self._add_common_flags() + self._add_common_flags(include_restart_strategy) + + def _add_common_flags(self, include_restart_strategy: bool): + """Add all common flags. - def _add_common_flags(self): - """Add all common flags.""" + Args: + include_restart_strategy: Whether to include the restart strategy flag. + """ namespace_group = self.parser.add_mutually_exclusive_group(required=True) namespace_group.add_argument( "-n", @@ -91,19 +90,15 @@ def _add_common_flags(self): help="Space separated list of cluster names for kubectl contexts", ) - restart_group = self.parser.add_mutually_exclusive_group() - restart_group.add_argument( - "-r", - "--restart-nodes", - action="store_true", - default=None, - help="Restart the pods after updating configuration (default behavior)", - ) - restart_group.add_argument( - "--no-restart", - action="store_true", - help="Do not restart the pods after updating configuration", - ) + if include_restart_strategy: + self.add_argument( + "-t", + "--restart-strategy", + type=restart_strategy_converter, + choices=list(RestartStrategy), + required=True, + help="Strategy for restarting nodes", + ) def add_argument(self, *args, **kwargs): """Add a new argument to the parser. @@ -126,27 +121,6 @@ def build(self) -> argparse.Namespace: return args -class Service(Enum): - """Service types mapping to their configmap and pod names.""" - - Core = ("sequencer-core-config", "sequencer-core-statefulset-0") - Gateway = ("sequencer-gateway-config", "sequencer-gateway-deployment") - HttpServer = ( - "sequencer-httpserver-config", - "sequencer-httpserver-deployment", - ) - L1 = ("sequencer-l1-config", "sequencer-l1-deployment") - Mempool = ("sequencer-mempool-config", "sequencer-mempool-deployment") - SierraCompiler = ( - "sequencer-sierracompiler-config", - "sequencer-sierracompiler-deployment", - ) - - def __init__(self, config_map_name: str, pod_name: str) -> None: - self.config_map_name = config_map_name - self.pod_name = pod_name - - def validate_arguments(args: argparse.Namespace) -> None: if (args.namespace_list and args.cluster_prefix) or ( args.namespace_prefix and args.cluster_list @@ -183,35 +157,6 @@ def validate_arguments(args: argparse.Namespace) -> None: sys.exit(1) -def get_namespace_list_from_args( - args: argparse.Namespace, -) -> list[str]: - """Get a list of namespaces based on the arguments""" - if args.namespace_list: - return args.namespace_list - - return [ - f"{args.namespace_prefix}-{i}" - for i in range(args.start_index, args.start_index + args.num_nodes) - ] - - -def get_context_list_from_args( - args: argparse.Namespace, -) -> list[str]: - """Get a list of contexts based on the arguments""" - if args.cluster_list: - return args.cluster_list - - if args.cluster_prefix is None: - return None - - return [ - f"{args.cluster_prefix}-{i}" - for i in range(args.start_index, args.start_index + args.num_nodes) - ] - - def get_logs_explorer_url( query: str, project_name: Optional[str] = None, @@ -229,22 +174,84 @@ def get_logs_explorer_url( ) -def run_kubectl_command(args: list, capture_output: bool = True) -> subprocess.CompletedProcess: - full_command = ["kubectl"] + args - try: - result = subprocess.run(full_command, capture_output=capture_output, text=True, check=True) - return result - except subprocess.CalledProcessError as e: - print_error(f"kubectl command failed: {' '.join(full_command)}") - print_error(f"Error: {e.stderr}") - sys.exit(1) +class ConfigValuesUpdater(ABC): + """Abstract class for updating configuration values for different service instances.""" + + def get_updated_config(self, orig_config_yaml: str, instance_index: int) -> str: + """Get updated configuration YAML for a specific instance. + + Args: + orig_config_yaml: Original configuration as YAML string + instance_index: Index of the instance to update configuration for + + Returns: + Updated configuration as YAML string + """ + config, config_data = parse_config_from_yaml(orig_config_yaml) + updated_config_data = self.get_updated_config_for_instance(config_data, instance_index) + return serialize_config_to_yaml(config, updated_config_data) + + @abstractmethod + def get_updated_config_for_instance( + self, config_data: dict[str, Any], instance_index: int + ) -> dict[str, Any]: + """Get updated configuration data for a specific instance. + + Args: + config_data: Current configuration data dictionary + instance_index: Index of the instance to update configuration for + + Returns: + Updated configuration data dictionary + """ -def get_namespace_args(namespace: str, cluster: Optional[str] = None) -> list[str]: - ret = ["-n", f"{namespace}"] - if cluster: - ret.extend(["--context", f"{cluster}"]) - return ret +class ConstConfigValuesUpdater(ConfigValuesUpdater): + """Concrete implementation that applies constant configuration overrides.""" + + def __init__(self, config_overrides: dict[str, Any]): + """Initialize with configuration overrides. + + Args: + config_overrides: Dictionary of configuration keys and values to override + """ + self.config_overrides = config_overrides + + def get_updated_config_for_instance( + self, config_data: dict[str, Any], instance_index: int + ) -> dict[str, Any]: + """Apply the same configuration overrides to the config data for each instance.""" + updated_config = config_data.copy() + + for key, value in self.config_overrides.items(): + print_colored(f" Overriding config: {key} = {value}") + updated_config[key] = value + + return updated_config + + +def get_current_block_number(feeder_url: str) -> int: + """Get the current block number from the feeder URL.""" + try: + url = f"https://{feeder_url}/feeder_gateway/get_block" + with urllib.request.urlopen(url) as response: + if response.status != 200: + raise urllib.error.HTTPError( + url, response.status, "HTTP Error", response.headers, None + ) + data = json.loads(response.read().decode("utf-8")) + current_block_number = data["block_number"] + return current_block_number + + except urllib.error.URLError as e: + print_error(f"Failed to fetch block number from feeder URL: {e}") + sys.exit(1) + except KeyError as e: + print_error(f"Unexpected response format from feeder URL: {e}") + sys.exit(1) + except json.JSONDecodeError as e: + print_error(f"Failed to parse JSON response from feeder URL: {e}") + sys.exit(1) def get_configmap( @@ -330,22 +337,6 @@ def represent_literal_str(dumper, data): return result -def update_config_values( - config_content: str, - config_overrides: dict[str, Any] = None, -) -> str: - """Update configuration values in the YAML content and return the updated YAML""" - # Parse the configuration - config, config_data = parse_config_from_yaml(config_content) - - for key, value in config_overrides.items(): - print_colored(f" Overriding config: {key} = {value}") - config_data[key] = value - - # Serialize back to YAML - return serialize_config_to_yaml(config, config_data) - - def normalize_config(config_content: str) -> str: """Normalize configuration by parsing and re-serializing without changes. @@ -379,26 +370,6 @@ def show_config_diff(old_content: str, new_content: str, index: int) -> None: print_colored("No changes detected", Colors.BLUE) -def ask_for_confirmation() -> bool: - """Ask user for confirmation to proceed""" - response = ( - input(f"{Colors.BLUE.value}Do you approve these changes? (y/n){Colors.RESET.value}") - .strip() - .lower() - ) - return response == "y" - - -def wait_until_y_or_n(question: str) -> bool: - """Wait until user enters y or n. Cotinues asking until user enters y or n.""" - while True: - response = input(f"{Colors.BLUE.value}{question} (y/n){Colors.RESET.value}").strip().lower() - if response == "y" or response == "n": - break - print_error(f"Invalid response: {response}") - return response == "y" - - def apply_configmap( config_content: str, namespace: str, @@ -421,95 +392,35 @@ def apply_configmap( sys.exit(1) -def restart_pod( - namespace: str, service: Service, index: int, cluster: Optional[str] = None -) -> None: - """Restart pod by deleting it""" - # Get the list of pods (one string per line). - kubectl_args = [ - "get", - "pods", - "-o", - "name", - ] - pods = run_kubectl_command(kubectl_args, capture_output=True).stdout.splitlines() - - # Filter the list of pods to only include the ones that match the service and extract the pod name. - pods = [pod.split("/")[1] for pod in pods if pod.startswith(f"pod/{service.pod_name}")] - - if not pods: - print_error(f"Could not find pods for service {service.pod_name}.") - sys.exit(1) - - # Go over each pod and delete it. - for pod in pods: - kubectl_args = [ - "delete", - "pod", - pod, - ] - kubectl_args.extend(get_namespace_args(namespace, cluster)) - - try: - run_kubectl_command(kubectl_args, capture_output=False) - print_colored(f"Restarted {pod} for node {index}") - except Exception as e: - print_error(f"Failed restarting {pod} for node {index}: {e}") - sys.exit(1) - - -def update_config_and_restart_nodes( - config_overrides: dict[str, Any], - namespace_list: list[str], +def _update_config( + config_values_updater: ConfigValuesUpdater, + namespace_and_instruction_args: NamespaceAndInstructionArgs, service: Service, - cluster_list: Optional[list[str]] = None, - restart_nodes: bool = True, - # TODO(guy.f): Remove this once we have metrics we use to decide based on. - wait_between_restarts: bool = False, - post_restart_instructions: Optional[list[str]] = None, ) -> None: - assert config_overrides is not None, "config_overrides must be provided" - assert namespace_list is not None and len(namespace_list) > 0, "namespaces must be provided" - - if post_restart_instructions is not None: - assert len(post_restart_instructions) == len( - namespace_list - ), f"logs_explorer_urls must have the same length as namespace_list. logs_explorer_urls: {len(post_restart_instructions)}, namespace_list: {len(namespace_list)}" - - if wait_between_restarts: - assert ( - post_restart_instructions is not None - ), "logs_explorer_urls must be provided when wait_between_restarts is True" - else: - assert ( - post_restart_instructions is None - ), "logs_explorer_urls must be None when wait_between_restarts is False" - - if not cluster_list: - print_colored( - "cluster-prefix/cluster-list not provided. Assuming all nodes are on the current cluster", - Colors.RED, - ) - else: - assert len(cluster_list) == len( - namespace_list - ), f"cluster_list must have the same number of values as namespace_list. cluster_list: {cluster_list}, namespace_list: {namespace_list}" - + """Update and apply configurations for all nodes.""" # Store original and updated configs for all nodes configs = [] # Process each node's configuration - for index, namespace in enumerate(namespace_list): - cluster = cluster_list[index] if cluster_list else None + for index in range(namespace_and_instruction_args.size()): + namespace = namespace_and_instruction_args.get_namespace(index) + cluster = namespace_and_instruction_args.get_cluster(index) + print_colored( - f"\nProcessing node for namespace {namespace} (cluster: {cluster if cluster else 'current cluster'})..." + f"\nProcessing node for namespace {namespace} (cluster: {cluster if cluster is not None else 'current cluster'})..." ) # Get current config and normalize it (e.g. " vs ') to ensure not showing bogus diffs. - original_config = normalize_config(get_configmap(namespace, cluster, service)) + original_config = normalize_config( + get_configmap( + namespace, + cluster, + service, + ) + ) # Update config - updated_config = update_config_values(original_config, config_overrides) + updated_config = config_values_updater.get_updated_config(original_config, index) # Store configs configs.append({"original": original_config, "updated": updated_config}) @@ -527,27 +438,32 @@ def update_config_and_restart_nodes( print(f"Applying config {index}...") apply_configmap( config["updated"], - namespace_list[index], + namespace_and_instruction_args.get_namespace(index), index, - cluster_list[index] if cluster_list else None, + namespace_and_instruction_args.get_cluster(index), ) - if restart_nodes: - for index, config in enumerate(configs): - restart_pod( - namespace_list[index], service, index, cluster_list[index] if cluster_list else None - ) - if wait_between_restarts: - instructions = post_restart_instructions[index] - print_colored(f"Restarted pod.\n{instructions}. ", Colors.YELLOW) - # Don't ask in the case of the last job. - if index != len(configs) - 1 and not wait_until_y_or_n( - f"Do you want to restart the next pod?" - ): - print_colored("\nAborting restart process.") - return - print_colored("\nAll pods have been successfully restarted!", Colors.GREEN) - else: - print_colored("\nSkipping pod restart (--no-restart was specified)") + +def update_config_and_restart_nodes( + config_values_updater: Optional[ConfigValuesUpdater], + namespace_and_instruction_args: NamespaceAndInstructionArgs, + service: Service, + restarter: ServiceRestarter, +) -> None: + assert namespace_and_instruction_args.namespace_list is not None, "namespaces must be provided" + + if not namespace_and_instruction_args.cluster_list: + print_colored( + "cluster-prefix/cluster-list not provided. Assuming all nodes are on the current cluster", + Colors.RED, + ) + + if config_values_updater is not None: + _update_config(config_values_updater, namespace_and_instruction_args, service) + + for index in range(namespace_and_instruction_args.size()): + if not restarter.restart_service(index): + print_colored("\nAborting restart process.") + sys.exit(1) print("\nOperation completed successfully!") diff --git a/scripts/prod/wait_for_cores_to_succesfully_propose.py b/scripts/prod/wait_for_cores_to_succesfully_propose.py new file mode 100755 index 00000000000..fe56230ec1f --- /dev/null +++ b/scripts/prod/wait_for_cores_to_succesfully_propose.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +import sys + +from common_lib import ( + NamespaceAndInstructionArgs, + RestartStrategy, + Service, + print_error, + wait_until_y_or_n, +) +from metrics_lib import MetricConditionGater +from restarter_lib import WaitOnMetricRestarter +from update_config_and_restart_nodes_lib import ( + ApolloArgsParserBuilder, + update_config_and_restart_nodes, +) + + +class WaitForProposalIncrease: + def __init__(self): + self.last_proposal_count = None + + def check_if_proposal_count_increased(self, proposal_count: int) -> bool: + if self.last_proposal_count is None: + # First time we're checking, so we don't know if the proposal count has increased. + # Save the current count and on the next increase we'll know we had a successful proposal. + self.last_proposal_count = proposal_count + + if proposal_count > self.last_proposal_count: + # Set the last proposal count to None so that next time we check we know we have to + # again first get the current value. + self.last_proposal_count = None + return True + + return False + + +def main(): + args_builder = ApolloArgsParserBuilder( + "Wait for each Core to successfully propose a block", + "python wait_for_cores_to_succesfully_propose.py -n apollo-sepolia-integration -m 3 -t all_at_once", + include_restart_strategy=False, + ) + args = args_builder.build() + + namespace_list = NamespaceAndInstructionArgs.get_namespace_list_from_args(args) + context_list = NamespaceAndInstructionArgs.get_context_list_from_args(args) + instructions = ["Checking node proposed successfully."] * len(namespace_list) + + namespace_and_instruction_args = NamespaceAndInstructionArgs( + namespace_list, + context_list, + instructions, + ) + + if not wait_until_y_or_n( + "Please update and or restart the first core as needed and press 'y' when ready to proceed." + ): + print_error("Operation cancelled by user") + sys.exit(1) + + proposal_increase_checker = WaitForProposalIncrease() + update_config_and_restart_nodes( + None, + namespace_and_instruction_args, + Service.Core, + WaitOnMetricRestarter( + namespace_and_instruction_args, + Service.Core, + [ + MetricConditionGater.Metric( + "consensus_decisions_reached_as_proposer", + proposal_increase_checker.check_if_proposal_count_increased, + ) + ], + 8082, + RestartStrategy.NO_RESTART, + ), + ) + + +if __name__ == "__main__": + main() diff --git a/workspace_tests/package_integrity_test.rs b/workspace_tests/package_integrity_test.rs index 246118ac4ca..f0ec4be0723 100644 --- a/workspace_tests/package_integrity_test.rs +++ b/workspace_tests/package_integrity_test.rs @@ -4,7 +4,8 @@ use toml_test_utils::{DependencyValue, PackageEntryValue, MEMBER_TOMLS}; /// Hard-coded list of crates that are allowed to use test code in their (non-dev) dependencies. /// Should only contain test-related crates. -static CRATES_ALLOWED_TO_USE_TESTING_FEATURE: [&str; 6] = [ +static CRATES_ALLOWED_TO_USE_TESTING_FEATURE: [&str; 7] = [ + "apollo_base_layer_tests", "apollo_integration_tests", "apollo_test_utils", "blockifier_test_utils",